Qiuzman
Qiuzman

Reputation: 1761

How to copy a single IFormFile to multiple memorystream concurrently

I have a IFormFile which I pass as a parameter to two separate asynchronous functions which convert the IFormFile to a memorystream which one uploads to OneDrive via ms graph and one uploads the other to Box via Box SDK. However, sometimes it works and sometimes it fails at the copyToAsync() line in OneDrive. It seems maybe you can only CopyTo one at a time? Because loading to these cloud services are slower I really want to use Async to make things a bit snappier. My code errors as follows:

The inner stream position has changed unexpectedly.
       at Microsoft.AspNetCore.Http.ReferenceReadStream.VerifyPosition()
   at Microsoft.AspNetCore.Http.ReferenceReadStream.ReadAsync(Memory`1 buffer, CancellationToken cancellationToken)
   at System.IO.Stream.<CopyToAsync>g__Core|27_0(Stream source, Stream destination, Int32 bufferSize, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Http.FormFile.CopyToAsync(Stream target, CancellationToken cancellationToken)
   at ARMS_API.Repositories.OneDriveRepository.UploadFileByPathAsync(IFormFile file, String DestinationPath, String RootFolderId) in /Users/baun/Documents/ARMS-API/ARMS-API/Repositories/OneDriveCloudStorage.cs:line 45
   at ARMS_API.Services.CertificationReviewService`1.AddCertificationAsync(IFormFile file, AddDocumentPOCO addDocumentPOCO) in /Users/baun/Documents/ARMS-API/ARMS-API/Features/CertificationReview/CertificationReviewService.cs:line 89
   at ARMS_API.Services.CertificationReviewService`1.AddCertificationReviewAsync(AddCertificationReviewDTO addCertificationReviewDTO) in /Users/baun/Documents/ARMS-API/ARMS-API/Features/CertificationReview/CertificationReviewService.cs:line 74
   at ARMS_API.Controllers.CertificationReviewController.AddCertificationReview(AddCertificationReviewDTO addCertificationReviewDTO) in /Users/baun/Documents/ARMS-API/ARMS-API/Features/CertificationReview/CertificationReviewController.cs:line 46

Implementation (I use a few of the same function but vary slightly):

        public async Task<List<DocumentDTO>> AddSupportingDocumentsAsync(AddDocumentsDTO addDocumentsDTO)
    {
        if(addDocumentsDTO.Files.Count > 0)
        {
            var currentUser = await _userService.GetByAccessTokenAsync();
            var addDocumentsPOCO = await _documentsRepository.AddSupportingDocumentsAsync(addDocumentsDTO, currentUser);

            var documentsDTO = new List<DocumentDTO>();
            var tasks = addDocumentsPOCO.Select( async file => {
                var document = addDocumentsDTO.Files.Where(item => item.FileName.Equals(file.FILENAME_AT_UPLOAD, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
                
                var oneDriveDocumentTask =  _oneDriveRepository.UploadFileByPathAsync(document!,Path.Combine(_armsBaseFolderPath, file.DOCUMENT_PATH!, file.DOCUMENT_FILENAME!),_rootOneDriveFolderId);
                var boxDocumentTask =  _boxRepository.UploadFileByPathAsync(document!,Path.Combine(file.DMCP_DOCUMENT_PATH!, file.DOCUMENT_FILENAME!),_rootBoxFolderId);
                List<Task> cloudStorageTasks = new List<Task>(){oneDriveDocumentTask,boxDocumentTask};

                await Task.WhenAll(cloudStorageTasks);

                documentsDTO.Add(new DocumentDTO{
                    MmcidForDisplay = file.MMCIDForDisplay,
                    Mmcid = file.MMCID,
                    FilenameAtUpload = file.FILENAME_AT_UPLOAD,
                    DocGroupTypeDd = file.DOC_GROUP_TYPE_DD,
                    DocIterationCount = file.DOC_ITERATION_COUNT,
                    DocTypeDd = file.DOC_TYPE_DD,
                    WhoUploaded = file.WHO_UPLOADED,
                    DmcpBoxId = file.DMCP_BOX_ID,
                    DmcpDocumentPath = file.DMCP_DOCUMENT_PATH,
                    OactDocumentPath = file.DOCUMENT_PATH,
                    DocumentFilename = file.DOCUMENT_FILENAME
                });
            });

            await Task.WhenAll(tasks);

            return documentsDTO;
        }

        return null;
    }

OneDrive:

public async Task<Models.CloudStorage?> UploadFileByPathAsync(IFormFile file, string DestinationPath, string RootFolderId)
{
    using(var memoryStream = new MemoryStream())
    {
        // Use properties to specify the conflict behavior
        // in this case, replace
        await file.CopyToAsync(memoryStream);
        memoryStream.Position = 0;

        var oneDriveUpload = await UploadOneDrive(memoryStream, DestinationPath, RootFolderId);
        return oneDriveUpload;
    }
}

private async Task<Models.CloudStorage?> UploadOneDrive(MemoryStream SourceFile, string DestinationPath, string RootFolderId)
{
    using var memoryStream = SourceFile;

    // Use properties to specify the conflict behavior
    // in this case, replace

    var uploadSessionRequestBody = new CreateUploadSessionPostRequestBody
    {
        Item = new DriveItemUploadableProperties
        {
            AdditionalData = new Dictionary<string, object>
            {
                { "@microsoft.graph.conflictBehavior", "replace" },
            },
        },
    };

    // Create the upload session
    // itemPath does not need to be a path to an existing item
    var uploadSession = await _graphServiceClient!.Drives[RootFolderId].Root
        .ItemWithPath(DestinationPath)
        .CreateUploadSession
        .PostAsync(uploadSessionRequestBody);


    // Max slice size must be a multiple of 320 KiB
    int maxSliceSize = 320 * 1024;
    var fileUploadTask =
        new LargeFileUploadTask<DriveItem>(uploadSession, memoryStream, maxSliceSize);

    var totalLength = memoryStream.Length;
    // Create a callback that is invoked after each slice is uploaded
    IProgress<long> progress = new Progress<long>(prog => {
        Console.WriteLine($"Uploaded {prog} bytes of {totalLength} bytes");
    });

    // Upload the file
    var uploadResult = await fileUploadTask.UploadAsync(progress);

    if (uploadResult.UploadSucceeded)
    {
        var response = uploadResult.ItemResponse;

        var cloudStorage = new Models.CloudStorage () {
            CloudStorageProvider = "OneDrive",
            Id = response.Id!,
            FileName = response.Name!,
            WebURL = response.WebUrl!,
            DownloadLink = null
        };

        return cloudStorage;
    }
    else
    {
        return null;
    }


}

Box:

public async Task<Models.CloudStorage?> UploadFileByPathAsync(IFormFile file, string DestinationPath, string RootFolderId)
    {
        using(var memoryStream = new MemoryStream())
        {
            await file.CopyToAsync(memoryStream);
            memoryStream.Position = 0;

            var boxUpload = await BoxUpload(memoryStream, DestinationPath, RootFolderId);
            return boxUpload;
        }
    }
    
    private async Task<Models.CloudStorage?> BoxUpload(MemoryStream SourceFile, string DestinationPath, string RootFolderId)
    {
        BoxClient adminClient = _session.AdminClient(await token.FetchTokenAsync());

        string[] directories = Path.GetDirectoryName(DestinationPath)!.Split(Path.DirectorySeparatorChar);
        string PrevParentFolderID = RootFolderId;
        
        //finding the folder
        try
        {
            foreach (var folderItem in directories)
            {
                var limit = 1000;
                var currentFolder = await adminClient.FoldersManager.GetFolderItemsAsync(id: PrevParentFolderID,1);
                var totalItems = currentFolder.TotalCount;

                for (int offset = 0; offset < totalItems; offset+=limit)
                {
                    var folderItems = await adminClient.FoldersManager.GetFolderItemsAsync(id: PrevParentFolderID, limit, offset);
                    if(folderItems.Entries.Any(x => x.Name.Equals(folderItem, StringComparison.OrdinalIgnoreCase)))
                    {
                        PrevParentFolderID = folderItems.Entries.Find(x => x.Name.Equals(folderItem, StringComparison.OrdinalIgnoreCase))!.Id;
                        break;
                    }
                    else
                    {
                        if(offset+limit>=totalItems)
                        {
                            try
                            {
                                // Create a new folder in the user's root folder
                                var folderParams = new BoxFolderRequest()
                                {
                                    Name = folderItem.ToString(),
                                    Parent = new BoxRequestEntity() { Id = PrevParentFolderID }
                                };
                                BoxFolder folder = await adminClient.FoldersManager.CreateAsync(folderParams);
                                PrevParentFolderID = folder.Id;
                                break;
                            }
                            catch(BoxException ex)
                            {
                                var ExJson = JsonSerializer.Deserialize<dynamic>(ex.Message)!;
                                PrevParentFolderID = ExJson["context_info"]["conflicts"][0]["id"];
                                break;
                            }
                        }
                    }
                }
            }
        }
        catch (BoxException ex)
        {
            dynamic ExJson = JsonSerializer.Deserialize<dynamic>(ex.Message)!;
            return null;
        }
        //file upload once folder id is found
        try
        {
            BoxFile file;

            using (var fileStream = SourceFile)
            {
                var fileSizeInMB = fileStream.Length / (1024 * 1024);
                if (fileSizeInMB <= 50 )
                {
                    BoxFileRequest requestParams = new BoxFileRequest()
                    {
                        Name = Path.GetFileName(DestinationPath),
                        Parent = new BoxRequestEntity() { Id = PrevParentFolderID }
                    };

                    file = await adminClient.FilesManager.UploadAsync(requestParams, fileStream);

                }
                else
                {
                    var progress = new Progress<BoxProgress>(val => {
                        Console.WriteLine("Uploaded {0}%", val.progress);
                    });

                    file = await adminClient.FilesManager.UploadUsingSessionAsync(fileStream, Path.GetFileName(DestinationPath), PrevParentFolderID, null, progress);
                    
                }
            }

            var cloudStorage = new Models.CloudStorage () {
                CloudStorageProvider = "Box",
                Id = file.Id,
                FileName = file.Name,
                WebURL = _productionDomain + "/file/" + file.Id,
                DownloadLink = null
            };

            return cloudStorage;
        }
        catch (BoxException ex)
        {
            dynamic ExJson = JsonSerializer.Deserialize<dynamic>(ex.Message)!;
            return null;
        }
    }

Upvotes: 0

Views: 57

Answers (0)

Related Questions