I have been trying to leverage the Microsoft Graph Api to communicate with user's one drive business inside a MVC web app, have set up everything with delegated permissions and can read and write data in logged-in user's ODB fine however is there any way by which nested folder or directory structure can be created?
Currently I am using code below to create the folder in the root of user's ODB and works fine but looking for a way to create hierarchy of the folders when path is provided before uploading the file in it.
DriveItem rootDirectory = graphServiceClient.Me.Drive.Root.Children.Request().AddAsync(new DriveItem
{
Name = "RootDirectory",
Folder = new Folder(),
}).Result;
And for another folder inside RootDirectory trying this but does not seem to work (where rootDirectory is object created above)
DriveItem subDirectory = graphServiceClient.Me.Drive.Root.Children.Request().AddAsync(new DriveItem
{
Name = "SubDirectory",
Folder = rootDirectory.Folder
}).Result;
Even if it works with some fix, not sure if it is most optimal way to do it, suggestions will be appreciated.
I made a small function to do that.
While it is true that the use of try-catch is not the best practice, in the end I think it is better than polling each folder recursively for its children, then lookup by name if part of the path is there.
public async Task CreateFolder(string foldername)
{
string[] splitted = foldername.Split(new string[] { "/" }, StringSplitOptions.RemoveEmptyEntries);
var f = graphServiceClient.Me.Drive.Root;
string p = "";
IDriveItemRequestBuilder b;
DriveItem driveItem;
foreach (string folder in splitted)
{
p = string.Format("{0}{1}{2}", p, string.IsNullOrEmpty(p) ? "" : "/", folder);
b = graphServiceClient.Me.Drive.Root.ItemWithPath(p);
try
{
driveItem = await b.Request().GetAsync();
}
catch
{
driveItem = null;
}
if (driveItem == null)
{
var f2 = await f.Children.Request().AddAsync(new DriveItem()
{
Name = folder,
Folder = new Folder()
});
b = graphServiceClient.Me.Drive.Root.ItemWithPath(p);
}
f = b;
}
}
and you call is like this:
await CreateFolder("folder1/folder2/folder3");
but looking for a way to create hierarchy of the folders when path is provided before uploading the file in it.
Based on my experience hierarchy of the folders is not supported by graphServiceClient currently.
If want to create a sub folder, it requires that parent folder is exist.
As a workaround, we could create the sub folder with following code. You also create a
recursive function to create the nested function
var folder= graphserviceClient.Me.Drive.Root.Children.Request()
.AddAsync(new DriveItem
{
Name = "tomfolder",
Folder = new Folder()
}).Result;
var subfolder = graphserviceClient.Me.Drive.Items[folder.Id].Children.Request()
.AddAsync(new DriveItem
{
Name = "subfolder",
Folder = new Folder()}
).Result;
And you also could give your good ideas to azure team.
I revisited the answer of Pic Mickael because it doesn't work for me (it just create two subfolders).
What I do is to create the first folder of my path so I have a start folder.
If I don't do this, when I try to add the drive item with an empty path I get an error.
So, let's create a root folder, first:
private static void CreateRootFolder(GraphServiceClient gc, string rootFolder)
{
var root = gc
.Drives[_driveId]
.Root
.Children
.Request()
.AddAsync(new DriveItem()
{
Name = rootFolder,
Folder = new Microsoft.Graph.Folder(),
AdditionalData = new Dictionary<string, object>
{
{
"#microsoft.graph.conflictBehavior", "replace"
}
}
})
.Result;
}
When I have my first folder, I can loop through all the others:
private static void CreateSubFolders(GraphServiceClient gc, string rootFolder, string foldername)
{
string[] splitted = foldername.Split(new string[] { "/" }, StringSplitOptions.RemoveEmptyEntries);
string pathCompleto = rootFolder;
foreach (string folder in splitted)
{
var driveItem = new DriveItem
{
Name = folder,
Folder = new Microsoft.Graph.Folder(),
AdditionalData = new Dictionary<string, object>
{
{
"#microsoft.graph.conflictBehavior", "replace"
}
}
};
var newFolder = gc
.Drives[_driveId]
.Root
.ItemWithPath(pathCompleto)
.Children
.Request()
.AddAsync(driveItem)
.Result;
pathCompleto = string.Format("{0}{1}{2}", pathCompleto, string.IsNullOrEmpty(pathCompleto) ? "" : "/", folder);
}
In the main program I will have two calls like this:
CreateRootFolder(_graphClient, "Folder00");
CreateSubFolders(_graphClient, "Folder00", "FolderAA/FolderBB/FolderCC/FolderDD/FolderEE");
This could be improved, but in my case it nicely resolve the problem.
Related
I'm very new to C# development.
I'm trying to check and create a folder/ sub-folder exist in Outlook Mailbox using Exchange Web Service.
Folder Structure
MAIN_folder
Sub Folder-1
Sub Folder-2
Sub Folder-3
Implementation
public void checkFolderExistOrNot( String folder_name)
{
FolderView fv = new FolderView(100);
var findFoldersResults = exchangeService.FindFolders(
WellKnownFolderName.Inbox,
new SearchFilter.SearchFilterCollection(
LogicalOperator.Or,
new SearchFilter.ContainsSubstring(FolderSchema.DisplayName, folder_name)),
fv);
foreach (var folder in findFoldersResults)
{
if (folder is Folder)
{
if (folder.DisplayName == folder_name)
{
archiveFolderID = folder.Id;
}
}
}
//if archive folder not found create and assign the variable to the folderID
if (archiveFolderID == null)
{
Folder folder = new Folder(exchangeService);
folder.DisplayName = folder_name;
folder.Save(WellKnownFolderName.Inbox);
archiveFolderID = folder.Id;
}
}
checkFolderExistOrNot(MAIN_folder)
checkFolderExistOrNot(MAIN_folder.Sub Folder-1)
checkFolderExistOrNot(MAIN_folder.Sub Folder-2)
checkFolderExistOrNot(MAIN_folder.Sub Folder-3)
But this is only creating the Main folder under the inbox. It would be greatly appreciated if someone could help me to identify what is missing in my implementation.
Thanks in Advance.
The only way to tell if a folder exists is to search for it with your search because you don't specify the traversal in the Folderview it will always be shallow. If you specify a deep traversal in
FolderView fv = new FolderView(100);
fv.Traversal = FolderTraversal.Deep;
You should then be able to find the parent folder you want to create a new subfolder on. Your logic should work okay as long as you don't have any name clashes a different folder levels. Otherwise what I do is this Exchange Web Service FolderId for a folder created by user or Get to an Exchange folder by path using EWS
Have you given Microsoft Graph a look?
You can basically use it for anything in Microsoft 365. With you you can also achieve your goal.
You will need to create a GraphServiceClient and with it you can do the following to check if a folder exists:
string user = "emailAddressOfTheUser";
var parentFolderRequest = graphClient.Users[user].MailFolders.Inbox.ChildFolders
.Request()
.Filter($"startsWith(displayName, 'parentFolderName')");
var parentMailFolder = await parentFolderRequest.GetAsync(cancellationToken);
Once you have the parent folder you can get it's ID and once you know that you can search it for child folders:
var parentMailFolderID = parentMailFolder.First().Id;
var childFolderRequest = graphClient.Users[user].MailFolders[parentMailFolderID].ChildFolders
.Request()
.Filter($"startsWith(displayName, 'childFolderName')");
var childMailFolder = await parentFolderRequest.GetAsync(cancellationToken);
If the childMailFolder.Count > 0 then the folder exists, if not you create the child folder:
var childFolder = new MailFolder
{
DisplayName = "childFolderName",
IsHidden = false
};
await graphClient.Users[graphUser.Id]
.MailFolders[parentMailFolderID].ChildFolders
.Request()
.AddAsync(childFolder );
I'm struggling with this basic operation.It will be nice if someone can write a working code.So let's say I got folder "AB" on desktop.Folder AB contains subfolder A and subfolder B.Subfolder A contains A.txt and Subfolder B contains B.txt.I want the user to simply choose folder AB via a browser dialog(I did that already) and then,when he clicks on a checkbox,file A.txt will go on subfolder B and B.txt will go on subfolder A.
I will do this for simple folders A and B. You will have to consider the chances of sub-folders as well.
string[] filesA = System.IO.Directory.GetFiles(AsourcePath);
string[] filesB = System.IO.Directory.GetFiles(BsourcePath);
foreach (string s in filesA)
{
System.IO.File.Move(s, AsourcePath);
}
foreach (string s in filesB)
{
System.IO.File.Move(s, BsourcePath);
}
Please Note: You will have consider so many scenarios for this including sub-folders, overwriting, existing files or folders etc.
Assuming that you have the folder path for both A and B folders,
var Afolder = #"D:\AB\A";
var Bfolder = #"D:\AB\B";
SwapFolderFiles(Afolder, Bfolder);
Pass the folder path for both A and B to SwapFolderFiles,
private static void SwapFolderFiles(string AFolder, string BFolder)
{
var AFolderfiles = System.IO.Directory.GetFiles(AFolder);
var BFolderfiles = System.IO.Directory.GetFiles(BFolder);
MoveFiles(AFolder, BFolder, AFolderfiles);
MoveFiles(BFolder, AFolder, BFolderfiles);
}
private static void MoveFiles(string sourceFolder, string destinationFolder, string[] folderfiles)
{
foreach (var file in folderfiles)
{
var filename = file.Substring(file.LastIndexOf("\\")+1);
var source = System.IO.Path.Combine(sourceFolder, filename);
var destination = System.IO.Path.Combine(destinationFolder, filename);
System.IO.File.Move(source, destination);
}
}
I'm trying to work how how to delete all files in a folder in Azure File Storage.
CloudFileDirectory.ListFilesAndDirectories() returns an IEnumerable of IListFileItem. But this doesn't help much because it doesn't have a filename property or similar.
This is what I have so far:
var folder = root.GetDirectoryReference("myfolder");
if (folder.Exists()) {
foreach (var file in folder.ListFilesAndDirectories()) {
// How do I delete 'file'
}
}
How can I change an IListFileItem to a CloudFile so I can call myfile.Delete()?
ListFilesAndDirectories can return both files and directories so you get a base class for those two. Then you can check which if the types it is and cast. Note you'll want to track any sub-directories so you can recursively delete the files in those.
var folder = root.GetDirectoryReference("myfolder");
if (folder.Exists())
{
foreach (var item in folder.ListFilesAndDirectories())
{
if (item.GetType() == typeof(CloudFile))
{
CloudFile file = (CloudFile)item;
// Do whatever
}
else if (item.GetType() == typeof(CloudFileDirectory))
{
CloudFileDirectory dir = (CloudFileDirectory)item;
// Do whatever
}
}
}
Took existing answers, fixed some bugs and created an extension method to delete the directory recursively
public static async Task DeleteAllAsync(this ShareDirectoryClient dirClient) {
await foreach (ShareFileItem item in dirClient.GetFilesAndDirectoriesAsync()) {
if (item.IsDirectory) {
var subDir = dirClient.GetSubdirectoryClient(item.Name);
await subDir.DeleteAllAsync();
} else {
await dirClient.DeleteFileAsync(item.Name);
}
}
await dirClient.DeleteAsync();
}
Call it like
var dirClient = shareClient.GetDirectoryClient("test");
await dirClient.DeleteAllAsync();
This recursive version works if you have 'directories' inside your 'directory'
public void DeleteOutputDirectory()
{
var share = _fileClient.GetShareReference(_settings.FileShareName);
var rootDir = share.GetRootDirectoryReference();
DeleteDirectory(rootDir.GetDirectoryReference("DirName"));
}
private static void DeleteDirectory(CloudFileDirectory directory)
{
if (directory.Exists())
{
foreach (IListFileItem item in directory.ListFilesAndDirectories())
{
switch (item)
{
case CloudFile file:
file.Delete();
break;
case CloudFileDirectory dir:
DeleteDirectory(dir);
break;
}
}
directory.Delete();
}
}
This implementation would be very easy to achieve with Recursion in PowerShell. Where you specify the directory [can be the root directory in your case] and then all contents of that directory including all files, subdirectories gets deleted. Refer to the github ready PowerShell for the same - https://github.com/kunalchandratre1/DeleteAzureFilesDirectoriesPowerShell
This method should do the trick - please comment if I'm wrong or it could be improved in any way.
public async override Task DeleteFolder(string storagePath)
{
var remaining = new Queue<ShareDirectoryClient>();
remaining.Enqueue(Share.GetDirectoryClient(storagePath));
while(remaining.Count > 0)
{
ShareDirectoryClient dir = remaining.Dequeue();
await foreach (ShareFileItem item in dir.GetFilesAndDirectoriesAsync())
{
if(item.IsDirectory)
{
var subDir = dir.GetSubdirectoryClient(item.Name);
await DeleteFolder(subDir.Path);
}
else
{
await dir
.GetFileClient(item.Name)
.DeleteAsync();
}
}
await dir.DeleteAsync();
}
}
Connect to your Azure container with a Virtual Machine (if file share, then go to fileshare > connect > and follow the commands to paste in your virtual machine - to connect to file share)
Connect to your container in the virtual machine command interface (cd 'location of your container')
Delete the folder (rm -rf 'folder to be deleted')
The accepted answer seems outdated now. The following snippet uses the latest sdk. To have a better performance It's implemented as a for loop not a recursive algorithm. It discovers all files and folders which are located at directoryPath. Once a file is discovered you can delete it.
var rootDirectory = directoryPath != null
? shareClient.GetDirectoryClient(directoryPath)
: shareClient.GetRootDirectoryClient();
var remaining = new Queue<ShareDirectoryClient>();
remaining.Enqueue(rootDirectory);
while (remaining.Count > 0)
{
var shareDirectoryClient = remaining.Dequeue();
await foreach (var item in shareDirectoryClient.GetFilesAndDirectoriesAsync())
{
if (!item.IsDirectory)
{
var shareFileClient = shareDirectoryClient.GetFileClient(item.Name);
files.Add(shareFileClient);
// do what you want
await shareFile.DeleteAsync();
}
if (item.IsDirectory)
{
remaining.Enqueue(shareDirectoryClient.GetSubdirectoryClient(item.Name));
// do what you want
await shareFile.DeleteAsync();
}
}
}
In the above code directory may be null or a path to a directory that you want to delete.
To Initialize the client, you can use the following method:
AccountSasBuilder sas = new AccountSasBuilder
{
Services = AccountSasServices.Files,
ResourceTypes = AccountSasResourceTypes.All,
ExpiresOn = DateTimeOffset.UtcNow.AddMonths(1)
};
sas.SetPermissions(AccountSasPermissions.List | AccountSasPermissions.Read | AccountSasPermissions.Delete);
var credential = new StorageSharedKeyCredential(AccountName, AccountKey);
var sasUri = new UriBuilder(AccountUri);
sasUri.Query = sas.ToSasQueryParameters(credential).ToString();
var shareServiceClient = new ShareServiceClient(sasUri.Uri);
var shareClient = shareServiceClient.GetShareClient(FileShareName);
I´m creating a web application that needs to list all the files and directories in a specific folder.
I´ve created the resurvice function to list these, but another specification is that if the directory has no files then the directory shouldn´t be shown.
Now the problem is, what if i have a directory with a submenu, with a submenu, with a submenu, with a submenu and only the last submenu has files in it. It still needs to show all the other directories.
I´ve no idea how to achieve this, any code or tips would be appreciated!
My current recursive function:
private List<FolderModel> GetFolderSubFolders(IFolderInfo folder)
{
var retval = new List<FolderModel>();
// Foreach subfolder in the given folder
foreach (var subFolder in FolderManager.Instance.GetFolders(folder))
{
// Create new foldermodel
var folderModel = new FolderModel
{
FolderName = subFolder.FolderName,
Bestanden = GetFolderBestanden(subFolder),
// Recall this function
SubFolders = GetFolderSubFolders(subFolder)
};
// Check if we have files and subfolders
if (folderModel.Bestanden.Any() && folderModel.SubFolders.Any())
{
folderModel.hasFilesAndFolders = true;
}
retval.Add(folderModel);
}
return retval;
}
Just let it find all directories and then filter for the ones which have files inside:
List<FolderModel> allFolders = GetFolderSubFolders(myFolder);
List<FolderModel> nonEmptyFolders = allFolders.Where(f => f.HasFiles).ToList();
I've not tried to run this but I think something like that should work:
private List<FolderModel> GetFolderSubFolders(IFolderInfo folder)
{
var retval = new List<FolderModel>();
// Foreach subfolder in the given folder
foreach (var subFolder in FolderManager.Instance.GetFolders(folder))
{
// Recall this function
List<FolderModel> subFolders = GetFolderSubFolders(subFolder);
// I am assuming that "Bestanden" is also a List<T> and contains
// the files of this folder
List<Something> bestanden = GetFolderBestanden(subFolder);
// Don't do anything if an empty list was returned
if(subFolders.Count > 0 || bestanden.Count > 0)
{
// Create new foldermodel
var folderModel = new FolderModel
{
FolderName = subFolder.FolderName,
Bestanden = bestanden,
SubFolders = subFolders
};
// Check if we have files and subfolders
if (folderModel.Bestanden.Any() && folderModel.SubFolders.Any())
{
folderModel.hasFilesAndFolders = true;
}
retval.Add(folderModel);
}
}
// If nothing was found this will return an empty list
return retval;
}
The idea is that you don't return anything if you don't find something at the last level. The above may not work as I've not run it but you may get a hint.
System.IO.CreateDirectory() is not available on .NET for Windows Store Apps.
How can I implement this equivalent method? StorageFolder.CreateFolderAsync() creates a subfolder inside the current folder, but in my case I have a path like and need to create all folders that doesn't exist in this path.
The path is inside the app's sandbox in windows.
There's no API with exactly the same behaviour of System.IO.CreateDirectory(), so I implemented it using Windows.Storage classes:
// Any and all directories specified in path are created, unless they already exist or unless
// some part of path is invalid. If the directory already exists, this method does not create a
// new directory.
// The path parameter specifies a directory path, not a file path, and it must in
// the ApplicationData domain.
// Trailing spaces are removed from the end of the path parameter before creating the directory.
public static void CreateDirectory(string path)
{
path = path.Replace('/', '\\').TrimEnd('\\');
StorageFolder folder = null;
foreach(var f in new StorageFolder[] {
ApplicationData.Current.LocalFolder,
ApplicationData.Current.RoamingFolder,
ApplicationData.Current.TemporaryFolder } )
{
string p = ParsePath(path, f);
if (f != null)
{
path = p;
folder = f;
break;
}
}
if(path == null)
throw new NotSupportedException("This method implementation doesn't support " +
"parameters outside paths accessible by ApplicationData.");
string[] folderNames = path.Split('\\');
for (int i = 0; i < folderNames.Length; i++)
{
var task = folder.CreateFolderAsync(folderNames[i], CreationCollisionOption.OpenIfExists).AsTask();
task.Wait();
if (task.Exception != null)
throw task.Exception;
folder = task.Result;
}
}
private static string ParsePath(string path, StorageFolder folder)
{
if (path.Contains(folder.Path))
{
path = path.Substring(path.LastIndexOf(folder.Path) + folder.Path.Length + 1);
return path;
}
return null;
}
To create folders outside your app's sandbox in windows store app, you'll have to add the document library in app-manifest, along with file permissions.
For a much more detailed explanation regarding library and documents, Refer this blog