DevExtreme v23.1 is now available.

Explore our newest features/capabilities and share your thoughts with us.

Your search did not match any results.
File Uploader

Direct Upload to Azure

The FileUploader component supports direct-upload to blob storages. This demo illustrates how to configure the uploadChunk property to upload a large file directly to Azure Blob Storage without using a user's web server. All APIs that implement access to Azure Blob Storage on the client are stored in the azure.file.system.js file (app.service.ts - for Angular framework).

To implement file upload logic, use the uploadChunk property to specify how to process a connection request to the storage.

Backend API
Copy to CodeSandBox
Apply
Reset
<div id="wrapper" [class]="wrapperClassName"> <dx-load-panel [position]="{ of: '#file-uploader' }" [(visible)]="loadPanelVisible" > </dx-load-panel> <div id="widget-area"> <dx-file-uploader id="file-uploader" [chunkSize]="200000" [maxFileSize]="1048576" [uploadChunk]="uploadChunk" > </dx-file-uploader> <div id="request-panel"> <div class="request-info" *ngFor="let request of requests"> <div class="parameter-info"> <div class="parameter-name">Method:</div> <div class="parameter-value dx-theme-accent-as-text-color">{{ request.method }}</div> </div> <div class="parameter-info"> <div class="parameter-name">Url path:</div> <div class="parameter-value dx-theme-accent-as-text-color">{{ request.urlPath }}</div> </div> <div class="parameter-info"> <div class="parameter-name">Query string:</div> <div class="parameter-value dx-theme-accent-as-text-color">{{ request.queryString }}</div> </div> <br /> </div> </div> </div> <div id="message-box"> To run the demo locally, specify your Azure storage account name, access key and container name in the web.config file. Refer to the <a href="https://js.devexpress.com/Demos/WidgetsGallery/Demo/FileUploader/AzureDirectUploading/Angular/Light/" target="_blank" rel="noopener noreferrer" > https://js.devexpress.com/Demos/WidgetsGallery/Demo/FileUploader/AzureDirectUploading/Angular/Light/</a > to see the demo online. </div> </div>
import { NgModule, Component, enableProdMode } from '@angular/core'; import { BrowserModule, BrowserTransferStateModule } from '@angular/platform-browser'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { HttpClient, HttpClientModule } from '@angular/common/http'; import { lastValueFrom } from 'rxjs'; import { DxFileUploaderModule, DxLoadPanelModule } from 'devextreme-angular'; import { AzureGateway } from './app.service'; if (!/localhost/.test(document.location.host)) { enableProdMode(); } @Component({ selector: 'demo-app', templateUrl: 'app/app.component.html', styleUrls: ['app/app.component.css'], preserveWhitespaces: true, }) export class AppComponent { requests: any[]; wrapperClassName: string; loadPanelVisible: boolean; constructor(http: HttpClient) { const endpointUrl = 'https://js.devexpress.com/Demos/Mvc/api/file-manager-azure-access'; gateway = new AzureGateway(endpointUrl, this.onRequestExecuted.bind(this)); this.requests = []; this.wrapperClassName = ''; this.loadPanelVisible = true; this.checkAzureStatus(http); } uploadChunk(file, uploadInfo) { let promise = null; if (uploadInfo.chunkIndex === 0) { promise = gateway.getUploadAccessUrl(file.name).then((accessUrls) => { uploadInfo.customData.accessUrl = accessUrls.url1; }); } else { promise = Promise.resolve(); } promise = promise.then(() => gateway.putBlock(uploadInfo.customData.accessUrl, uploadInfo.chunkIndex, uploadInfo.chunkBlob)); if (uploadInfo.chunkIndex === uploadInfo.chunkCount - 1) { promise = promise.then(() => gateway.putBlockList(uploadInfo.customData.accessUrl, uploadInfo.chunkCount)); } return promise; } onRequestExecuted({ method, urlPath, queryString }) { const request = { method, urlPath, queryString }; this.requests.unshift(request); } checkAzureStatus(http: HttpClient) { lastValueFrom(http.get<{ active: boolean }>('https://js.devexpress.com/Demos/Mvc/api/file-manager-azure-status?widgetType=fileManager')) .then((result) => { this.wrapperClassName = result.active ? 'show-widget' : 'show-message'; this.loadPanelVisible = false; }); } } let gateway = null; @NgModule({ imports: [ BrowserModule, BrowserTransferStateModule, DxFileUploaderModule, DxLoadPanelModule, HttpClientModule, ], declarations: [AppComponent], bootstrap: [AppComponent], }) export class AppModule { } platformBrowserDynamic().bootstrapModule(AppModule);
#widget-area { visibility: hidden; } #message-box { display: none; } .show-widget #widget-area { visibility: visible; } .show-message #widget-area { display: none; } .show-message #message-box { display: block; } #request-panel { min-width: 505px; height: 400px; overflow-x: hidden; overflow-y: auto; padding: 18px; margin-top: 40px; background-color: rgba(191, 191, 191, 0.15); } #request-panel .parameter-info { display: flex; } .request-info .parameter-name { flex: 0 0 100px; } .request-info .parameter-name, .request-info .parameter-value { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
export class AzureFileSystem { gateway: AzureGateway; EMPTY_DIR_DUMMY_BLOB_NAME: string; constructor(azureGateway) { this.gateway = azureGateway; this.EMPTY_DIR_DUMMY_BLOB_NAME = 'aspxAzureEmptyFolderBlob'; } getItems(path) { var prefix = this.getDirectoryBlobName(path); return this.gateway.getBlobList(prefix).then((entries) => this.getDataObjectsFromEntries(entries, prefix)); } createDirectory(path, name) { var blobName = path ? `${path}/${name}` : name; return this.gateway.createDirectoryBlob(blobName); } renameFile(path, name) { var newPath = this.getPathWithNewName(path, name); return this.moveFile(path, newPath); } renameDirectory(path, name) { var newPath = this.getPathWithNewName(path, name); return this.moveDirectory(path, newPath); } getPathWithNewName(path, name) { var parts = path.split('/'); parts[parts.length - 1] = name; return parts.join('/'); } deleteFile(path) { return this.gateway.deleteBlob(path); } deleteDirectory(path) { var prefix = this.getDirectoryBlobName(path); return this.executeActionForEachEntry(prefix, (entry) => this.gateway.deleteBlob(entry.name)); } copyFile(sourcePath, destinationPath) { return this.gateway.copyBlob(sourcePath, destinationPath); } copyDirectory(sourcePath, destinationPath) { var prefix = this.getDirectoryBlobName(sourcePath); var destinationKey = this.getDirectoryBlobName(destinationPath); return this.executeActionForEachEntry(prefix, (entry) => this.copyEntry(entry, prefix, destinationKey)); } copyEntry(entry, sourceKey, destinationKey) { var restName = entry.name.substr(sourceKey.length); var newDestinationKey = destinationKey + restName; return this.gateway.copyBlob(entry.name, newDestinationKey); } moveFile(sourcePath, destinationPath) { return this.gateway.copyBlob(sourcePath, destinationPath).then(() => this.gateway.deleteBlob(sourcePath)); } moveDirectory(sourcePath, destinationPath) { var prefix = this.getDirectoryBlobName(sourcePath); var destinationKey = this.getDirectoryBlobName(destinationPath); return this.executeActionForEachEntry(prefix, (entry) => this.copyEntry(entry, prefix, destinationKey).then(() => this.gateway.deleteBlob(entry.name))); } downloadFile(path) { this.gateway.getBlobUrl(path).then((accessUrls: AccessUrls) => { window.location.href = accessUrls.url1; }); } executeActionForEachEntry(prefix, action) { return this.gateway.getBlobList(prefix).then((entries) => { var deferreds = entries.map((entry) => action(entry)); return Promise.all(deferreds); }); } getDataObjectsFromEntries(entries, prefix) { var result = []; var directories = {}; entries.forEach((entry) => { var restName = entry.name.substr(prefix.length); var parts = restName.split('/'); if (parts.length === 1) { if (restName !== this.EMPTY_DIR_DUMMY_BLOB_NAME) { var obj = { name: restName, isDirectory: false, dateModified: entry.lastModified, size: entry.length, }; result.push(obj); } } else { var dirName = parts[0]; var directory = directories[dirName]; if (!directory) { directory = { name: dirName, isDirectory: true, }; directories[dirName] = directory; result.push(directory); } if (!directory.hasSubDirectories) { directory.hasSubDirectories = parts.length > 2; } } }); result.sort(this.compareDataObjects); return result; } compareDataObjects(obj1, obj2) { if (obj1.isDirectory === obj2.isDirectory) { var name1 = obj1.name.toLowerCase(); var name2 = obj2.name.toLowerCase(); if (name1 < name2) { return -1; } return name1 > name2 ? 1 : 0; } return obj1.isDirectory ? -1 : 1; } getDirectoryBlobName(path) { return path ? `${path}/` : path; } } export class AzureGateway { endpointUrl: any; onRequestExecuted: any; constructor(endpointUrl, onRequestExecuted) { this.endpointUrl = endpointUrl; this.onRequestExecuted = onRequestExecuted; } getBlobList(prefix) { return this.getAccessUrl('BlobList') .then((accessUrls: AccessUrls) => this.executeBlobListRequest(accessUrls.url1, prefix)) .then((xml) => this.parseEntryListResult(xml)); } parseEntryListResult(xmlString): Array<FileEntry> { var xml = new DOMParser().parseFromString(xmlString, 'text/xml'); return Array.from(xml.querySelectorAll('Blob')).map(this.parseEntry); } parseEntry(xmlEntry): FileEntry { var entry: FileEntry = {}; entry.etag = xmlEntry.querySelector('Etag').textContent; entry.name = xmlEntry.querySelector('Name').textContent; var dateStr = xmlEntry.querySelector('Last-Modified').textContent; entry.lastModified = new Date(dateStr); var lengthStr = xmlEntry.querySelector('Content-Length').textContent; entry.length = parseInt(lengthStr, 10); return entry; } executeBlobListRequest(accessUrl, prefix) { var params: RequestParams = { restype: 'container', comp: 'list', }; if (prefix) { params.prefix = prefix; } return this.executeRequest(accessUrl, params); } createDirectoryBlob(name) { return this.getAccessUrl('CreateDirectory', name).then( (accessUrls: AccessUrls) => this.executeRequest({ url: accessUrls.url1, method: 'PUT', headers: { 'x-ms-blob-type': 'BlockBlob', }, processData: false, contentType: false, }), ); } deleteBlob(name) { return this.getAccessUrl('DeleteBlob', name).then( (accessUrls: AccessUrls) => this.executeRequest({ url: accessUrls.url1, method: 'DELETE', }), ); } copyBlob(sourceName, destinationName) { return this.getAccessUrl('CopyBlob', sourceName, destinationName).then( (accessUrls: AccessUrls) => this.executeRequest({ url: accessUrls.url2, method: 'PUT', headers: { 'x-ms-copy-source': accessUrls.url1, }, }), ); } putBlock(uploadUrl, blockIndex, blockBlob) { var blockId = this.getBlockId(blockIndex); var params: RequestParams = { comp: 'block', blockid: blockId, }; return this.executeRequest( { url: uploadUrl, method: 'PUT', data: blockBlob, processData: false, contentType: false, }, params, ); } putBlockList(uploadUrl, blockCount) { var content = this.getBlockListContent(blockCount); var params: RequestParams = { comp: 'blocklist', }; return this.executeRequest( { url: uploadUrl, method: 'PUT', data: content, }, params, ); } getBlockListContent(blockCount) { var contentParts = [ '<?xml version="1.0" encoding="utf-8"?>', '<BlockList>', ]; for (var i = 0; i < blockCount; i++) { var blockContent = ` <Latest>${this.getBlockId(i)}</Latest>`; contentParts.push(blockContent); } contentParts.push('</BlockList>'); return contentParts.join('\n'); } getBlockId(blockIndex: number): string { var res = `${blockIndex}`; while (res.length < 10) { res = `0${res}`; } return window.btoa(res); } getUploadAccessUrl(blobName: string): Promise<AccessUrls | any> { return this.getAccessUrl('UploadBlob', blobName); } getBlobUrl(blobName: string): Promise<AccessUrls | any> { return this.getAccessUrl('GetBlob', blobName); } getAccessUrl( command: string, blobName?: string, blobName2?: string, ): Promise<AccessUrls | any> { var url = `${this.endpointUrl}?command=${command}`; if (blobName) { url += `&blobName=${encodeURIComponent(blobName)}`; } if (blobName2) { url += `&blobName2=${encodeURIComponent(blobName2)}`; } // eslint-disable-next-line no-async-promise-executor return new Promise(async (resolve, reject) => { await this.executeRequest(url).then((x) => { if (x.success) { resolve({ url1: x.accessUrl, url2: x.accessUrl2 }); } else { reject(x.error); } }); }); } executeRequest(args: any, commandParams?: RequestParams): Promise<any> { var ajaxArgs = typeof args === 'string' ? { url: args } : args; var method = ajaxArgs.method || 'GET'; var urlParts = ajaxArgs.url.split('?'); var urlPath = urlParts[0]; var restQueryString = urlParts[1]; var commandQueryString = commandParams ? this.getQueryString(commandParams) : ''; var queryString = commandQueryString || ''; if (restQueryString) { queryString += queryString ? `&${restQueryString}` : restQueryString; } ajaxArgs.url = queryString ? `${urlPath}?${queryString}` : urlPath; return fetch(ajaxArgs.url, ajaxArgs) .then((x) => { var eventArgs = { method, urlPath, queryString, }; if (this.onRequestExecuted) { this.onRequestExecuted(eventArgs); } return x; }) .then(async (x) => { if (x.status === 200) { var text = await x.text(); try { return { success: true, ...JSON.parse(text) }; } catch (ex) { return text; } } else { return { error: x.statusText }; } }); } getQueryString(params: object): string { return Object.keys(params) .map((key) => `${key}=${encodeURIComponent(params[key])}`) .join('&'); } } type FileEntry = { etag?: string; name?: string; lastModified?: Date; length?: number; }; type RequestParams = { restype?: string; comp?: string; prefix?: string; blockid?: string; }; export type AccessUrls = { url1: string; url2: string; };
// In real applications, you should not transpile code in the browser. // You can see how to create your own application with Angular and DevExtreme here: // https://js.devexpress.com/Documentation/Guide/Angular_Components/Getting_Started/Create_a_DevExtreme_Application/ window.config = { transpiler: 'ts', typescriptOptions: { module: 'system', emitDecoratorMetadata: true, experimentalDecorators: true, }, meta: { 'typescript': { 'exports': 'ts', }, 'devextreme/localization.js': { 'esModule': true, }, }, paths: { 'npm:': 'https://unpkg.com/', }, map: { 'ts': 'npm:plugin-typescript@4.2.4/lib/plugin.js', 'typescript': 'npm:typescript@4.2.4/lib/typescript.js', '@angular/core': 'npm:@angular/core@12.2.17', '@angular/platform-browser': 'npm:@angular/platform-browser@12.2.17', '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic@12.2.17', '@angular/forms': 'npm:@angular/forms@12.2.17', '@angular/common': 'npm:@angular/common@12.2.17', '@angular/compiler': 'npm:@angular/compiler@12.2.17', 'tslib': 'npm:tslib@2.3.1/tslib.js', 'rxjs': 'npm:rxjs@7.5.3/dist/bundles/rxjs.umd.js', 'rxjs/operators': 'npm:rxjs@7.5.3/dist/cjs/operators/index.js', 'rrule': 'npm:rrule@2.6.4/dist/es5/rrule.js', 'luxon': 'npm:luxon@1.28.1/build/global/luxon.min.js', 'es6-object-assign': 'npm:es6-object-assign@1.1.0', 'devextreme': 'npm:devextreme@23.1.5/cjs', 'devextreme/bundles/dx.all': 'npm:devextreme@23.1.5/bundles/dx.all.js', 'jszip': 'npm:jszip@3.7.1/dist/jszip.min.js', 'devextreme-quill': 'npm:devextreme-quill@1.6.2/dist/dx-quill.min.js', 'devexpress-diagram': 'npm:devexpress-diagram@2.2.1', 'devexpress-gantt': 'npm:devexpress-gantt@4.1.48', 'devextreme-angular': 'npm:devextreme-angular@23.1.5', '@devextreme/runtime': 'npm:@devextreme/runtime@3.0.11', 'inferno': 'npm:inferno@7.4.11/dist/inferno.min.js', 'inferno-compat': 'npm:inferno-compat/dist/inferno-compat.min.js', 'inferno-create-element': 'npm:inferno-create-element@7.4.11/dist/inferno-create-element.min.js', 'inferno-dom': 'npm:inferno-dom/dist/inferno-dom.min.js', 'inferno-hydrate': 'npm:inferno-hydrate@7.4.11/dist/inferno-hydrate.min.js', 'inferno-clone-vnode': 'npm:inferno-clone-vnode/dist/inferno-clone-vnode.min.js', 'inferno-create-class': 'npm:inferno-create-class/dist/inferno-create-class.min.js', 'inferno-extras': 'npm:inferno-extras/dist/inferno-extras.min.js', // Prettier 'prettier/standalone': 'npm:prettier@2.8.4/standalone.js', 'prettier/parser-html': 'npm:prettier@2.8.4/parser-html.js', }, packages: { 'app': { main: './app.component.ts', defaultExtension: 'ts', }, 'devextreme': { defaultExtension: 'js', }, 'devextreme/events/utils': { main: 'index', }, 'devextreme/events': { main: 'index', }, 'es6-object-assign': { main: './index.js', defaultExtension: 'js', }, 'rxjs': { defaultExtension: 'js', }, 'rxjs/operators': { defaultExtension: 'js', }, }, packageConfigPaths: [ 'npm:@devextreme/*/package.json', 'npm:@devextreme/runtime@3.0.11/inferno/package.json', 'npm:@angular/*/package.json', 'npm:@angular/common@12.2.17/*/package.json', 'npm:rxjs@7.5.3/package.json', 'npm:rxjs@7.5.3/operators/package.json', 'npm:devextreme-angular@23.1.5/*/package.json', 'npm:devextreme-angular@23.1.5/ui/*/package.json', 'npm:devextreme-angular@23.1.5/package.json', 'npm:devexpress-diagram@2.2.1/package.json', 'npm:devexpress-gantt@4.1.48/package.json', ], }; System.config(window.config);
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>DevExtreme Demo</title> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" /> <link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/23.1.5/css/dx.light.css" /> <link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/23.1.5/css/dx-gantt.css" /> <script src="https://unpkg.com/core-js@2.6.12/client/shim.min.js"></script> <script src="https://unpkg.com/zone.js@0.12.0/dist/zone.js"></script> <script src="https://unpkg.com/reflect-metadata@0.1.13/Reflect.js"></script> <script src="https://unpkg.com/systemjs@0.21.3/dist/system.js"></script> <script src="config.js"></script> <script> System.import("app").catch(console.error.bind(console)); </script> </head> <body class="dx-viewport"> <div class="demo-container"> <demo-app>Loading...</demo-app> </div> </body> </html>
using DevExtreme.MVC.Demos.Models.FileManagement; using System; using System.Net.Http; using System.Web.Http; using Azure.Storage; using Azure.Storage.Sas; using Azure.Storage.Blobs; using Azure.Storage.Blobs.Specialized; namespace DevExtreme.MVC.Demos.Controllers.ApiControllers { public class FileUploaderAzureAccessApiController : ApiController { const long MaxBlobSize = 1048576; const string ServiceUri = "https://{0}.blob.core.windows.net"; BlobServiceClient _client; BlobServiceClient Client { get { if(_client == null) { AzureStorageAccount accountModel = AzureStorageAccount.FileUploader.Value; StorageSharedKeyCredential credential = new StorageSharedKeyCredential(accountModel.AccountName, accountModel.AccessKey); _client = new BlobServiceClient(new Uri(string.Format(ServiceUri, accountModel.AccountName)), credential); } return _client; } } BlobContainerClient _container; BlobContainerClient Container { get { if(_container == null) { AzureStorageAccount accountModel = AzureStorageAccount.FileUploader.Value; _container = Client.GetBlobContainerClient(accountModel.ContainerName); } return _container; } } [HttpGet] [Route("api/file-uploader-azure-access", Name = "FileUploaderAzureAccessApi")] public object Process(string command, string blobName) { try { return UploadBlob(blobName); } catch { return CreateErrorResult(); } } object UploadBlob(string blobName) { if(blobName.Contains("/")) return CreateErrorResult("Invalid blob name."); string prefix = Guid.NewGuid().ToString("N"); string fullBlobName = $"{prefix}_{blobName}"; var blob = Container.GetBlockBlobClient(fullBlobName); if(blob.Exists() && blob.GetProperties().Value.ContentLength > MaxBlobSize) { return CreateErrorResult(); } if(blob.CanGenerateSasUri) { var sasUri = blob.GenerateSasUri(BlobSasPermissions.Write, DateTimeOffset.UtcNow.AddHours(1)); return CreateSuccessResult(sasUri.AbsoluteUri); } else { return CreateErrorResult("BlobClient cannot generate SasUri"); } } object CreateSuccessResult(string url, string url2 = null) { return new { success = true, accessUrl = url, accessUrl2 = url2 }; } object CreateErrorResult(string error = null) { if(string.IsNullOrEmpty(error)) error = "Unspecified error."; return new { success = false, error = error }; } } }