File attachments
Overview
Virtual datamodels define file attachments using FileReference datatype, but the file content is physically located in ContentManager service.
External application is using file upload / download functionality using REST API to manipulate with files and their content.
See 'Services - Content Management' articles in 'Development documentation' section.
See also:
ContentManager API endpoints
ContentManager API base URL: https://{hostname}/api/asol/cnt
GET methods
1. Get a file-info by fileId
GET /api/v1/Files/{id}/Info
- parameters:
id- (mandatory) the file identifieraccessLevel- the data access level (Public= shared /Private= tenant, used for data integration)
2. Get a file-info by filePath
GET /api/v1/Files/Info/Path/{path}
- parameters:
path- (mandatory) the file pathaccessLevel- the data access level (Public= shared /Private= tenant, used for data integration)
- sample of response (shortened):
{
"metadata": {
"mimeType": "image/jpeg",
"length": 14410,
"hashCode": "JYq4fwSJY42fdMu2RDbr/5X2VoNu0zoKg4T08HIH7bhjhZKDc0bYT8tq5Bm3bc0jWovq3RBieeaUBqQV8QehTA=="
},
"name": "eureka.jpg",
"extension": ".jpg",
"sharingType": "Restricted",
"folderId": "1f117395-c0ed-4746-9a06-72515d1dbfb9",
"folder": {
"id": "1f117395-c0ed-4746-9a06-72515d1dbfb9",
"name": "Test",
"fullName": "ASOL/Test"
},
"fullName": "ASOL/Test/eureka.jpg",
"hiddenForPath": false,
"id": "1c6c91e4-5b9d-418e-a266-2fd481b9a4f5"
}
3. Get a file-content by fileId
GET /api/v1/Files/{id}
- parameters:
id- (mandatory) the file identifieraccessLevel- the data access level (Public= shared /Private= tenant, used for data integration)
4. Get a file-content by filePath
GET /api/v1/Files/Path/{path}
- parameters:
path- (mandatory) the file pathaccessLevel- the data access level (Public= shared /Private= tenant, used for data integration)
- sample of response headers (shortened):
content-disposition: attachment; filename=eureka.jpg; filename*=UTF-8''eureka.jpg
content-length: 14410
content-type: image/jpeg
etag: "JYq4fwSJY42fdMu2RDbr/5X2VoNu0zoKg4T08HIH7bhjhZKDc0bYT8tq5Bm3bc0jWovq3RBieeaUBqQV8QehTA=="
last-modified: Tue,16 Feb 2021 14:22:36 GMT
POST methods
1. Upload file
POST /api/v1/FileUpload
- parameters:
id- (mandatory) the file identifieraccessLevel- the data access level (Public= shared /Private= tenant, used for data integration)
- sample of request headers (shortened):
content-length: 14410
content-type: multipart/form-data; boundary=----WebKitFormBoundaryjWK4lVDs8qHYPQn4
------WebKitFormBoundaryjWK4lVDs8qHYPQn4
Content-Disposition: form-data; name="file"; filename="eureka.jpg"
Content-Type: image/jpeg
------WebKitFormBoundaryjWK4lVDs8qHYPQn4--
- response properties:
uploadId- the unique identifier of uploaded file contentfileName- the filename with extensionfileExtension- the file extensionfileSize- the file sizemimeType- the mime type of fileuploadedBytes- the amount of uploaded bytes
- sample of response:
{
"uploadId": "4d6f2614-89b7-420d-95d0-2c9b18b8d4a8",
"fileName": "eureka.jpg",
"fileExtension": ".jpg",
"fileSize": 14410,
"mimeType": "image/jpeg",
"uploadedBytes": 14410
}
2. Create file for uploaded file-content
POST /api/v1/Files/{uploadId}
- parameters:
id- (mandatory) the file identifieraccessLevel- the data access level (Public= shared /Private= tenant, used for data integration)
- request body properties:
sharingType- (mandatory) the sharing type (Restricted= authenticated access, used for data integration)folderId- (mandatory) the folder identifiername- the filename with or without extension (the uploaded filename is used when not defined)appendExtension: (optional) the flag to append original extension to filenameoverwrite- (optional) the flag to overwrite file in filepathhiddenForPath- (optional) the flag to hide file from filepath (file isn't accessible by filepath)
- sample of request body:
{
"sharingType": "Restricted",
"folderId": "1f117395-c0ed-4746-9a06-72515d1dbfb9",
"name": "eureka.jpg",
"overwrite": true
}
- api endpoint returns file-info in response, see: GET methods
3. Copy file from existing file and clone file-content
POST /api/v1/Files/{id}/Copy
- parameters:
id- (mandatory) the original file identifieraccessLevel- the original data access level (Private= tenant, used for data integration)
- request body properties:
targetAccess- (mandatory) the target data access level (Private= tenant, used for data integration)sharingType- (mandatory) the target sharing type (Restricted= authenticated access, used for data integration)folderId- (mandatory) the target folder identifiername- the target filename with or without extension (the original filename is used when not defined)appendExtension: (optional) the flag to append original extension to target filenameoverwrite- (optional) the flag to overwrite target file in filepathhiddenForPath- (optional) the flag to hide file from filepath (file isn't accessible by filepath)
- sample of request body:
{
"targetAccess": "Private",
"sharingType": "Restricted",
"folderId": "1f117395-c0ed-4746-9a06-72515d1dbfb9",
"name": "eureka.jpg",
"overwrite": true
}
- api endpoint returns file-info in response, see: GET methods
ContentManager connector for backend usage
- nuget package:
ASOL.ContentManager.Connector
Register ContentManager connector
// dependency injection configuration
using ASOL.ContentManager.Connector.Extensions;
using ASOL.ContentManager.Connector.Options;
services.AddApiConnectorInfrastructure();
services.Configure<ContentManagerClientOptions>(Configuration.GetSection(nameof(ContentManagerClientOptions)));
services.AddContentManagerClient();
// appsettings.json
{
// ...
"ContentManagerClientOptions": {
"BaseUrl": "https://[hostname]/api/asol/cnt"
},
// ...
}
Consume data with file attachments
Examples - get fileinfo with hashCode by fileId or filePath
using ASOL.ContentManager.Connector;
using ASOL.DataService.Edge.Contracts.DataTypes;
// get fileinfo and hashCode by fileId from ContentManager
var fileInfo = await cntClient.GetFileAsync(fileReference.Source.AccessLevel, fileReference.Source.FileId, acceptNotFound: false, ct: cancellationToken);
var hashCode = fileInfo.Metadata.HashCode;
// get fileinfo and hashCode by filePath from ContentManager
var fileInfo = await cntClient.GetFileByPathAsync(fileReference.Source.AccessLevel, fileReference.Source.FilePath, acceptNotFound: false, ct: cancellationToken);
var hashCode = fileInfo.Metadata.HashCode;
Examples - download filecontent by fileId or filePath
using ASOL.ContentManager.Connector;
using ASOL.DataService.Edge.Contracts.DataTypes;
// download filecontent by fileId from ContentManager (authenticated access for any of sharing types)
var fileContentResult = await cntClient.FileDownloadAsync(fileReference.Source.AccessLevel, fileReference.Source.FileId, cancellationToken);
using var stream = fileContentResult.ReadContentAsStreamAsync();
// download filecontent by filePath from ContentManager (authenticated access for any of sharing types)
var fileContentResult = await cntClient.FileDownloadByPathAsync(fileReference.Source.AccessLevel, fileReference.Source.FilePath, cancellationToken);
using var stream = fileContentResult.ReadContentAsStreamAsync();
Publish data with file attachments
Examples - initialize folder structure by folderPath and get base folder for files
using ASOL.ContentManager.Connector;
using ASOL.ContentManager.Contracts;
using ASOL.ContentManager.Contracts.Primitives;
using ASOL.ContentManager.Contracts.Primitives.Filters;
using ASOL.Core.Multitenancy.Contracts;
using ASOL.Core.Paging.Contracts.Filters;
using ASOL.DataService.Edge.Contracts.DataTypes;
// recursive algorithm to create root path for your application
var folderPath = $"{applicationOwner}/{applicationCode}/{mandantCode}/{area}"; //recommended path
var accessLevel = DataAccessLevel.Private;
FolderModelBase folderInfo = null;
foreach (var folderName in folderPath.Split('/'))
{
var customFilter = new FolderSearchFilter { BaseFolderId = folderInfo?.Id, ExactPathMatch = true, FolderPath = folderName };
var folders = await cntClient.SearchFoldersAsync(accessLevel, customFilter, new PagingFilter { Limit = 1 }, cancellationToken);
folderInfo = folders.FirstOrDefault();
if (folderInfo == null)
{
folderInfo = await cntClient.CreateFolderAsync(accessLevel, new FolderModelCreate { Name = folderName, ParentId = folderInfo.Id }, cancellationToken);
}
}
var baseFolderId = folderInfo.Id;
Examples - upload and create file in Content Manager and prepare the file reference to the file
using ASOL.ContentManager.Connector;
using ASOL.ContentManager.Contracts;
using ASOL.ContentManager.Contracts.Primitives;
using ASOL.Core.Multitenancy.Contracts;
using ASOL.DataService.Edge.Contracts.DataTypes;
// upload filecontent into ContentManager
var uploadStatus = await cntClient.FileUploadAsync(accessLevel, fileName, stream, mimeType, cancellationToken);
var uploadId = uploadStatus.UploadId;
// create fileinfo in ContentManager
var fileRequest = new FileModelCreate { FolderId = rootFolderId, SharingType = SharingType.Restricted, Overwrite = true };
var fileInfo = await cntClient.CreateFileAsync(accessLevel, fileRequest, uploadId, cancellationToken);
// create file reference
var fileReference = new FileReference
{
FileName = preferredFileName,
ReferenceId = referenceId,
Source = new FileIdentifier { AccessLevel = accessLevel, FileId = fileInfo.Id, FilePath = fileInfo.FullName, FolderId = fileInfo.FolderId },
};
Examples - clone file in "consume to publish" scenario (instead of download and upload content to the new file)
using ASOL.ContentManager.Connector;
using ASOL.ContentManager.Contracts;
using ASOL.ContentManager.Contracts.Primitives;
using ASOL.Core.Multitenancy.Contracts;
using ASOL.DataService.Edge.Contracts.DataTypes;
// clone file instead of create file from downloaded and uploaded content
var originalFileReference = ... ; // obtained from consumed data
var copyRequest = new FileModelCopy { TargetAccess = targetAccessLevel, Name = targetFileName, FolderId = targetFolderId, SharingType = SharingType.Restricted, Overwrite = true };
var fileInfo = await cntClient.CopyFileAsync(originalFileReference.Source.AccessLevel, originalFileReference.Source.FileId, copyRequest, cancellationToken);