Download and upload DriveItem from shared OneDrive Folder with MS Graph SDK - c#

I'm currently trying to implement several tasks that involve listing, uploading and downloading files from a shared OneDrive folder. This folder is accesible via the logged in users OneDrive (visible in his root folder). The listing part works pretty well so far, using this code:
string remoteDriveId = string.Empty;
private GraphServiceClient graphClient { get; set; }
// Get the root of the owners OneDrive
DriveItem ownerRoot = await this.graphClient.Drive.Root.Request().Expand("thumbnails,children($expand=thumbnails)").GetAsync();
// Select the shared folders general information
DriveItem sharedFolder = ownerRoot.Children.Where(c => c.Name == "sharedFolder").FirstOrDefault();
// Check if it is a remote folder
if(sharedFolder.Remote != null)
{
remoteDriveId = item.RemoteItem.ParentReference.DriveId;
// Get complete Information of the shared folder
sharedFolder = await graphClient.Drives[remoteDriveId].Items[sharedFolder.RemoteItem.Id].Request().Expand("thumbnails,children").GetAsync();
}
So obviously I need to retrieve the shared folders information from the OneDrive that shared it with the other OneDrive.
Next part for me is to list the contents of this shared folder, which also works pretty well like this:
foreach (DriveItem child in sharedFolder.Children)
{
DriveItem childItem = await graphClient.Drives[remoteDriveId].Items[child.Id].Request().Expand("thumbnails,children").GetAsync();
if(childItem.Folder == null)
{
string path = Path.GetTempPath() + Guid.NewGuid();
// Download child item to path
}
}
My problem starts with the "Download child item to path" part. There I want to download everything, that is not a folder to a temporary file. The problem is that OneDrive always answers my request with an error message, that the file was not found. What I tried so far is:
using (var stream = await graphClient.Drives[remoteDriveId].Items[childItem.Id].Content.Request().GetAsync())
using (var outputStream = new System.IO.FileStream(path, System.IO.FileMode.Create))
{
await stream.CopyToAsync(outputStream);
}
In another variant I tried to use the ID of the childItem ParentReference (but I think this will only lead me to the remote OneDrives ID of sharedFolder):
using (var stream = await graphClient.Drives[remoteDriveId].Items[childItem.ParentReference.Id].Content.Request().GetAsync())
using (var outputStream = new System.IO.FileStream(path, System.IO.FileMode.Create))
{
await stream.CopyToAsync(outputStream);
}
After Downloading the files I want to edit them and reupload them to a different path in the shared folder. That path is created by me (which allready works) like this:
DriveItem folderToCreate = new DriveItem { Name = "folderName", Folder = new Folder() };
await graphClient.Drives[remoteDriveId].Items[sharedFolder.Id].Children.Request().AddAsync(folderToCreate);
The upload then fails. I've tried it like this:
using (var stream = new System.IO.FileStream(#"C:\temp\testfile.txt", System.IO.FileMode.Open))
{
await graphClient.Drives[remoteDriveId].Items[sharedFolder.Id].Content.Request().PutAsync<DriveItem>(stream);
}
And also like this (which works if it is not a shared folder and I therefore use Drive instead of Drives):
using (var stream = new System.IO.FileStream(#"C:\temp\testfile.txt", System.IO.FileMode.Open))
{
string folderPath = sharedFolder.ParentReference == null ? "" : sharedFolder.ParentReference.Path.Remove(0, 12) + "/" + Uri.EscapeUriString(sharedFolder.Name);
var uploadPath = folderPath + "/" + uploadFileName;
await graphClient.Drives[remoteDriveId].Root.ItemWithPath(uploadPath).Content.Request().PutAsync<DriveItem>(stream);
}
I couldn't get the AddAsync method (like in the folder creation) to work because I don't know how to create a DriveItem from a Stream.
If somebody could point me in the right direction I would highly appreciate that! Thank you!

The request:
graphClient.Drives[remoteDriveId].Items[childItem.ParentReference.Id].Content.Request().GetAsync()
corresponds to Download the contents of a DriveItem endpoint and is only valid if childItem.ParentReference.Id refers to a File resource, in another cases it fails with expected exception:
Microsoft.Graph.ServiceException: Code: itemNotFound Message: You
cannot get content for a folder
So, to download content from a folder the solution would be to:
enumerate items under folder: GET /drives/{drive-id}/items/{folderItem-id}/children
per every item explicitly download its content if driveItem corresponds to a File facet: GET /drives/{drive-id}/items/{fileItem-id}/content
Example
var sharedItem = await graphClient.Drives[driveId].Items[folderItemId].Request().Expand(i => i.Children).GetAsync();
foreach (var item in sharedItem.Children)
{
if (item.File != null)
{
var fileContent = await graphClient.Drives[item.ParentReference.DriveId].Items[item.Id].Content.Request()
.GetAsync();
using (var fileStream = new FileStream(item.Name, FileMode.Create, System.IO.FileAccess.Write))
fileContent.CopyTo(fileStream);
}
}
Example 2
The example demonstrates how to download file from a source folder and upload it into a target folder:
var sourceDriveId = "--source drive id goes here--";
var sourceItemFolderId = "--source folder id goes here--";
var targetDriveId = "--target drive id goes here--";
var targetItemFolderId = "--target folder id goes here--";
var sourceFolder = await graphClient.Drives[sourceDriveId].Items[sourceItemFolderId].Request().Expand(i => i.Children).GetAsync();
foreach (var item in sourceFolder.Children)
{
if (item.File != null)
{
//1. download a file as a stream
var fileContent = await graphClient.Drives[item.ParentReference.DriveId].Items[item.Id].Content.Request()
.GetAsync();
//save it into file
//using (var fileStream = new FileStream(item.Name, FileMode.Create, System.IO.FileAccess.Write))
// fileContent.CopyTo(fileStream);
//2.Upload file into target folder
await graphClient.Drives[targetDriveId]
.Items[targetItemFolderId]
.ItemWithPath(item.Name)
.Content
.Request()
.PutAsync<DriveItem>(fileContent);
}
}
Instead of downloading/uploading file content, i think what you are actually after is DriveItem copy or move operations. Lets say there are files that needs to be copied from one (source) folder into another (target), then the following example demonstrates how to accomplish it:
var sourceDriveId = "--source drive id goes here--";
var sourceItemFolderId = "--source folder id goes here--";
var targetDriveId = "--target drive id goes here--";
var targetItemFolderId = "--target folder id goes here--";
var sourceFolder = await graphClient.Drives[sourceDriveId].Items[sourceItemFolderId].Request().Expand(i => i.Children).GetAsync();
foreach (var item in sourceFolder.Children)
{
if (item.File != null)
{
var parentReference = new ItemReference
{
DriveId = targetDriveId,
Id = targetItemFolderId
};
await graphClient.Drives[sourceDriveId].Items[item.Id]
.Copy(item.Name, parentReference)
.Request()
.PostAsync();
}
}
}

Related

Is there any way to upload a folder(have subfolders also) instead of file using .netcore web api

Is it possible to upload the entire folder with same folder structure using .netcore webapi. I know we can zip and upload, then unzip, just wondering for alternative way to upload as it is instead of zipping
Currently i have updated the controller as below, this logic doesnot consider folders for upload
private async Task<bool> Import()
{
bool isSaveSuccess = false;
string fileName;
var files = Request.Form.Files;
foreach (var file in files)
{
try
{
var extn = "." + file.FileName.Split('.')[file.FileName.Split('.').Length - 1];
fileName = file.FileName + DateTime.Now.Ticks + extn;
//fileName = Path.Combine(file.FileName, DateTime.Now.Ticks + extn);
var pathBuilt = Path.Combine(Directory.GetCurrentDirectory(), "Upload\\files1");
if (!Directory.Exists(pathBuilt))
{
Directory.CreateDirectory(pathBuilt);
}
var path = Path.Combine(Directory.GetCurrentDirectory(), "Upload\\files1", fileName);
using (var stream = new FileStream(path, FileMode.Create))
{
await file.CopyToAsync(stream);
}
isSaveSuccess = true;
}
catch
{
_logger.LogError("Request failed: " + "err");
}
}
return isSaveSuccess;
}
Yes, it should be possible. Have a look at webkitdirectory property: https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/webkitdirectory
This will let you select one or more folders, so you can iterate over the inner contents of the users selection. As you can see, this is a "native" feature and not limited to asp.net or c# in any way.
<input type="file" id="files" name="files" webkitdirectory multiple />
<ul id="listing"></ul>
document.getElementById("filepicker").addEventListener("change", function(event) {
let output = document.getElementById("listing");
let files = event.target.files;
for (let i=0; i<files.length; i++) {
let item = document.createElement("li");
item.innerHTML = files[i].webkitRelativePath;
output.appendChild(item);
};
}, false);
As you can see from the demo code, you can get the relative path by calling .webkitRelativePath, so yit should be no problem to recreate the folder structure on your target.
Be aware, that this feature is not natively available on Internet Explorer and Firefox versions on android below 50.
Update for saving files with subfolders
Here is a bare minimum example of uploading taking any number of uploaded form files and saving them to disk, while keeping the relative folder structure.
First change your controllers constructor to get a your current environment in:
private readonly IWebHostEnvironment _env;
public HomeController(IWebHostEnvironment environment)
{
_env = environment;
}
Then amend your upload logic accordingly:
[HttpPost]
public async Task<IActionResult> Upload([FromForm] IFormFileCollection files)
{
//target in wwwroot for static files
string targetFolder = Path.Combine(_env.WebRootPath, "uploads");
foreach (IFormFile file in files)
{
if (file.Length <= 0) continue;
//fileName is the the fileName including the relative path
string path = Path.Combine(targetFolder, file.FileName);
//check if folder exists, create if not
var fi = new FileInfo(path);
fi.Directory?.Create();
//copy to target
using var fileStream = new FileStream(path, FileMode.Create);
await file.CopyToAsync(fileStream);
}
return View("Index");
}
This will result to your folder and all its subfolders being saved into your wwwroot/uploads/ folder inside your application:
wwwroot/
|
|-uploads
|-MyFolder
|- File1.jpg
|- File2.jpg
|- MyFolder2
|- File3.jpg
I'd recommend saving them somewhere different though, because redeploying might clean out the application folder first, depending on your deployment strategy (xcopy, github actions, azure pipelines, containers ...)

Dynamic saving of files .net core

I have this function that save the file in the wwwroot foldar:
[HttpPost]
public async Task<IActionResult> UploadFile(IFormFile file)
{
if (file == null || file.Length == 0)
return Content("file not selected");
var path = Path.Combine( Directory.GetCurrentDirectory(), "wwwroot", file.FileName);
using (var stream = new FileStream(path, FileMode.Create))
{
await file.CopyToAsync(stream);
}
return RedirectToAction("Files");
}
I'm trying to save the files dynamically so that every time a file is uploaded the function will check whether there is a folder with the user ID (from the session). If the folder exists, it is saved there, otherwise, it will open a new folder with his ID.
I want to create a sub folder for each userId and save user specific file to that folder
I added this and now it'ts working
var userId = HttpContext.Session.GetString("UserId");
if (!Directory.Exists(Path.Combine(
Directory.GetCurrentDirectory(), $"wwwroot/{userId}")))
{
Directory.CreateDirectory(Path.Combine(
Directory.GetCurrentDirectory(), $"wwwroot/{userId}"));
}
The easiest way to get the current user's id is:
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
Then, you just work this into your path:
var path = Path.Combine( Directory.GetCurrentDirectory(), $"wwwroot/users/{userId}", file.FileName);

This method or property is not supported after HttpRequest.GetBufferlessInputStream has been invoked

I am trying to browse and upload a file from client to server using Angular Js and WEB API.I used Input file type for user to select file and post the file to WEB API. In web API, I am getting following error "This method or property is not supported after HttpRequest.GetBufferlessInputStream has been invoked."
I am using the following code:-
public IHttpActionResult UploadForm()
{
HttpResponseMessage response = new HttpResponseMessage();
var httpRequest = System.Web.HttpContext.Current.Request;
if (httpRequest.Files.Count > 0)
{
foreach (string file in httpRequest.Files)
{
var postedFile = httpRequest.Files[file];
var filePath = System.Web.HttpContext.Current.Server.MapPath("~/UploadFile/" + postedFile.FileName);
postedFile.SaveAs(filePath);
}
}
return Json("Document Saved");
}
I get this error when i tried to get files from HTTP request... should I update anything in web config??
Please help me to resolve this issue..
try this it work fine for me.
//get the root folder where file will be store
string root = HttpContext.Current.Server.MapPath("~/UploadFile");
// Read the form data.
var provider = new MultipartFormDataStreamProvider(root);
await Request.Content.ReadAsMultipartAsync(provider);
if (provider.FileData.Count > 0 && provider.FileData[0] != null)
{
MultipartFileData file = provider.FileData[0];
//clean the file name
var fileWithoutQuote = file.Headers.ContentDisposition.FileName.Substring(1, file.Headers.ContentDisposition.FileName.Length - 2);
//get current file directory on the server
var directory = Path.GetDirectoryName(file.LocalFileName);
if (directory != null)
{
//generate new random file name (not mandatory)
var randomFileName = Path.Combine(directory, Path.GetRandomFileName());
var fileExtension = Path.GetExtension(fileWithoutQuote);
var newfilename = Path.ChangeExtension(randomFileName, fileExtension);
//Move file to rename existing upload file name with new random filr name
File.Move(file.LocalFileName, newfilename);
}
}
I also had the same problem. And the solution by #Jean did not work for me.
I need to upload some CSV file and had to use it in the controller.
In Javascript, I used Fetch API to upload the csv file.
But, in the controller, I used this code:
[HttpPost]
[CatchException]
public bool ImportBundlesFromCsv()
{
var a = Request.Content.ReadAsByteArrayAsync();
//convert to Stream if needed
Stream stream = new MemoryStream(a.Result); // a.Result is byte[]
// convert to String if needed
string result = System.Text.Encoding.UTF8.GetString(a.Result);
// your code
return true;
}
This worked for me. Hope this helps!

Why I can't upload sqlite file to the OneDrive?

I am developing an Windows Runtime Universal Application.
I need to upload the database I'm using in my application to the user's OneDrive?
But FileNotFound exception occurs. But I know the path is correct.
Since I refer the sqlite file, it shows the exception. If I refer txt file, Uploading process goes smoothly.
var authClient = new LiveAuthClient();
var authResult = await authClient.LoginAsync(new string[] { "wl.skydrive", "wl.skydrive_update" });
if (authResult.Session != null)
{
var liveConnectClient = new LiveConnectClient(authResult.Session);
var FileToUpload = await ApplicationData.Current.LocalFolder.GetFileAsync("text.sqlite");//exception occurs here
var FileToUpload = await ApplicationData.Current.LocalFolder.GetFileAsync("text.txt");//no exception for txt files
var folderData = new Dictionary<string, object>();
folderData.Add("name", "Folder")
LiveOperationResult operationResult = await liveConnectClient.PostAsync("me/skydrive", folderData);
LiveUploadOperation uploadOperation = await liveConnectClient.CreateBackgroundUploadAsync(folderId, "filename", FileToUpload, OverwriteOption.Overwrite);
LiveOperationResult uploadResult = await uploadOperation.StartAsync();
HandleUploadResult(uploadResult);
}
First, you should use a background transfer task to move a file that large and to handle the occasion when the user gets a call during transfer. Background tasks are the key.
http://code.msdn.microsoft.com/windowsapps/Background-Transfer-Sample-d7833f61/sourcecode?fileId=52027&pathId=1495533284
Sort of like this.
BackgroundUploader uploader = new BackgroundUploader();
UploadOperation upload = uploader.CreateUpload(uri, file);
await HandleUploadAsync(upload, true);
But before you even do that, you need to assume that the user will manipulate the database. As a result, you should copy the database file before you start the upload. Again, code:
var sourceFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
var sourceFile = await sourceFolder.CreateFileAsync("database",
Windows.Storage.CreationCollisionOption.OpenIfExists);
var targetFolder = await sourceFolder.CreateFolderAsync("~",
Windows.Storage.CreationCollisionOption.OpenIfExists);
var targetFile = await targetFolder.CreateFileAsync(sourceFile.Name,
Windows.Storage.CreationCollisionOption.ReplaceExisting);
await sourceFile.MoveAndReplaceAsync(targetFile);
This is not only a good idea, I have a feeling it will correct the problems you are having. Remember you can always query and ask if a transfer is done (do you aren't doing two).
Best of luck!

Exception on Extracting the Zip file in C#

I am working on Extraction Code to Extract Zip file, Using C# in winrt.
I am getting File from Local Drive here:
StorageFile file = await KnownFolders.PicturesLibrary.GetFileAsync("dostoyevsky-poor-folk.zip");
Stream zipMemoryStream = await file.OpenStreamForReadAsync();
var folder = ApplicationData.Current.LocalFolder;
// Create zip archive to access compressed files in memory stream
using (ZipArchive zipArchive = new ZipArchive(zipMemoryStream, ZipArchiveMode.Read))
{
// For each compressed file...
foreach (ZipArchiveEntry entry in zipArchive.Entries)
{
if (entry.Name == "")
{
// Folder
await CreateRecursiveFolder(folder, entry);
}
else
{
// File
await ExtractFile(folder, entry);
}
}
}
I am Extracting For folder here:
private async Task CreateRecursiveFolder(StorageFolder folder, ZipArchiveEntry entry)
{
var steps = entry.FullName.Split('/').ToList();
steps.RemoveAt(steps.Count() - 1);
foreach (var i in steps)
{
await folder.CreateFolderAsync(i, CreationCollisionOption.OpenIfExists);
folder = await folder.GetFolderAsync(i);
}
}
I am Extracting For File Here:
private async Task ExtractFile(StorageFolder folder, ZipArchiveEntry entry)
{
var steps = entry.FullName.Split('/').ToList();
steps.RemoveAt(steps.Count() - 1);
foreach (var i in steps)
{
folder = await folder.GetFolderAsync(i);
}
using (Stream fileData = entry.Open())
{
StorageFile outputFile = await folder.CreateFileAsync(entry.Name, CreationCollisionOption.ReplaceExisting);
using (Stream outputFileStream = await outputFile.OpenStreamForWriteAsync())
{
await fileData.CopyToAsync(outputFileStream);
await outputFileStream.FlushAsync();
}
}
}
When I try to use this I get this exception: 'System.NullReferenceException' .
The Exception getting line is the Last line of await outputFileStream.FlushAsync();
Some times I am getting same exception when I try to pick file from Local Drive.
Before Getting Exception the Debugger value of await outputFileStream.FlushAsync() looking like this.
Can you Help me out for this.
Thanks
Finally, It is Worked for me. Why because I am getting Null value while extraction Because of the package where I am going to extract files.
I am sure this is the perfect solution to Extract Zip file for windows store apps using c#.
Thanks

Categories