Download complete Sharepoint folder with subfolders and files with C#.
I recently had the problem, that I needed to download a complete folder from Sharepoint (Microsoft Teams) recusivly to my local drive. Here is my solution. Prequesite is that we already have an authenticated ClientContext, which we will use in the code (_context
).
The main function
The main function I am using, first the URL is normalized, you can find the function here. The normalization extracts the part of the URL we can use for accessing the file. Example:
URL: https://site.sharepoint.com/sites/Team/Documents/General/folder/html Normalized: /sites//Team/General/folder/html
In the next step, we use some regex, to get the end of the path (the folder we want to download), „html“ in the example above, the rest is the base url.
Then we can use the URL to read the folders and build the file list. After that we process the file list and build the download list. In the end we download the file list, we gathered.
// <summary> /// Download a Sharepoint folder /// </summary> /// <param name="url"></param> /// <param name="targetFolder"></param> public void DownloadFolder(string url, string targetFolder) { url = NormalizeUrl(url); var baseUrl = ""; var urlFolderName = ""; { var match = new Regex("(?<base>(.*))\\/(?<folder>.+)").Match(url); if (match.Success) { baseUrl = match.Groups["base"].Value.Trim(); urlFolderName = match.Groups["folder"].Value.Trim(); } } var rootFolders = _context.Web.GetFolderByServerRelativeUrl(baseUrl).Folders; _context.Load(rootFolders, folders => folders.Include(f => f.ListItemAllFields)); _context.ExecuteQuery(); var sharepointList = new List<string>(); foreach (var folder in rootFolders) { if (folder.ListItemAllFields.FieldValues.ContainsKey("FileLeafRef")) { var folderName = (string)folder.ListItemAllFields.FieldValues["FileLeafRef"]; if (folderName == urlFolderName) { GetFilesAndFolders(_context, folder, "", sharepointList); } } } var downloadItemList = new List<DownloadItem>(); //list for local files //create paths for further processing foreach (var item in sharepointList) { var temp = item.Replace("/", "\\"); temp = temp.Replace(urlFolderName + "\\", "").Trim('\\'); var localFileName = Path.Combine(targetFolder, temp); var remotePath = baseUrl + item; var downloadItem = new DownloadItem { LocalTargetPath = localFileName, RemotePath = remotePath }; downloadItemList.Add(downloadItem); } //download items DownloadItems(downloadItemList); }
Build the file list for download
In order to build the list of files, we use a recursive function, which is reading all files and folders from the location. The list is returned from the function.
/// <summary> /// Get all files, folder (recursive) from a sharepoint location /// </summary> /// <param name="context"></param> /// <param name="folder"></param> /// <param name="folderName"></param> /// <param name="sharepointList"></param> private void GetFilesAndFolders(ClientContext context, Folder folder, string folderName, List<string> sharepointList) { var fileList = ""; if (folder != null && folder.ListItemAllFields.FieldValues.Count > 0) { folderName += "/" + (string)folder.ListItemAllFields.FieldValues["FileLeafRef"]; fileList += "Folder:" + folderName + Environment.NewLine; var fileCollection = folder.Files; context.Load(fileCollection, files => files.Include(f => f.ListItemAllFields)); context.ExecuteQuery(); foreach (var file in fileCollection) { var fileName = folderName + "/" + (string)file.ListItemAllFields.FieldValues["FileLeafRef"]; fileList += fileName + Environment.NewLine; sharepointList.Add(fileName); } var subFolderCollection = folder.Folders; context.Load(subFolderCollection, folders => folders.Include(f => f.ListItemAllFields)); context.ExecuteQuery(); foreach (var subFolder in subFolderCollection) { GetFilesAndFolders(context, subFolder, folderName, sharepointList); } } }
Download the file
For downloading the items, we have a list of download items, the download item is very simple:
public class DownloadItem { /// <summary> /// the local path where the downloaded file should be put at /// </summary> public string LocalTargetPath { get; set; } = string.Empty; /// <summary> /// the remote path where the file should be downloaded from /// </summary> public string RemotePath { get; set; } = string.Empty; public override string ToString() { return RemotePath + " - " + LocalTargetPath; } }
The download function:
/// <summary> /// Downloads a file from sharepoint /// </summary> /// <param name="link"></param> /// <param name="targetDocument"></param> public void DownloadFile(string link, string targetDocument) { var relativeUrl = NormalizeUrl(link); var fileInfo = File.OpenBinaryDirect(_context, relativeUrl); using (Stream destination = System.IO.File.Create(targetDocument)) { for (var a = fileInfo.Stream.ReadByte(); a != -1; a = fileInfo.Stream.ReadByte()) destination.WriteByte((byte)a); } }
Usage
Now we have everything prepared, we can use or class for download like that.
var url = "https://site.sharepoint.com/sites/Team/Documents/General/folder/html"; var targetFolder = "c:\\temp\\sharepoint"; SharepointAccess.DownloadFolder(url, targetFolder);
This will download the sharepoint folder to its target location. And well thats it!
Update
In the comments was asked about the „DownloadItems“ function, here it is:
/// <summary> /// Download the list of items /// </summary> /// <param name="items"></param> private void DownloadItems(List<DownloadItem> items) { foreach (var item in items) { var targetFolder = Path.GetDirectoryName(item.LocalTargetPath); if (targetFolder != null && !Directory.Exists(targetFolder)) { Directory.CreateDirectory(targetFolder); } DownloadFile(item.RemotePath, item.LocalTargetPath); } }
Hallo ist es möglich die ganze Klasse zu bekommen inklusive der Usings
Hab eine E-Mail geschrieben.
Mir bitte auch die ganze Klasse 😉
Im Sample oben fehlt die DownloadItems Funktion
Sorry für die späte Antwort, aber ich habe die Funktion in den Blogartikel eingefügt. Gruß Andy