Retrieve All Documents from all Subfolders in a Document Library - CSOM - sharepoint

I am using the client side object model approach C# in order to retrieve all list items from a document library containing sub folders. I checked out the MSDN documentation and I am stuck as to why I cannot get the field property, or if I am even doing this right.
NetworkCredential credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
ClientContext clientcontext = new ClientContext(Resources.defaultSPSite);
clientcontext.Credentials = credentials;
//Load Libraries from SharePoint
//Web site = clientcontext.Web;
clientcontext.Load(clientcontext.Web.Lists);
clientcontext.ExecuteQuery();
//List sharedDocumentsList = clientcontext.Web.Lists.GetByTitle("TestLDOCS");
//CamlQuery camlQuery = new CamlQuery();
//camlQuery.ViewXml = #"<View Scope='Recursive'><Query></Query></View>";
foreach (List list in clientcontext.Web.Lists)
{
clientcontext.Load(list);
clientcontext.ExecuteQuery();
//list.TemplateFeatureId.ToString().Equals("") &&
string baseType = list.BaseType.ToString();
string listTitle = list.Title.ToString();
if (list.BaseType.ToString().Equals("DocumentLibrary", StringComparison.InvariantCultureIgnoreCase) && list.Title.ToString().Equals("TestLDOCS", StringComparison.InvariantCultureIgnoreCase))
{
foreach (Folder subFolder in list.RootFolder.Folders)
{
foreach (File f in subFolder.Files)
{
Console.WriteLine((string) f.Title);
}
}
}
}
}
The error that I am receiving is that the "foreach(File f in subFolder.Files)" collection may not be initialized error. Is there anyway to get the field values of all documents in every subfolder within a document library using CSOM?
I know you can strongly type the field values as well with a list item ie (listItem["fieldName"]). Should I go this route instead?

Some recommendations:
1) Prefer ClientRuntimeContext.LoadQuery method to load a specific lists, for example:
var lists = ctx.LoadQuery(ctx.Web.Lists.Where(l => l.BaseType == BaseType.DocumentLibrary));
ctx.ExecuteQuery();
2) Since SharePoint SCOM supports Request Batching it is recommended to minimize the number of requests to the server. The following example demonstrates how to perform a single request to the server in order to load all files from document libraries:
foreach (var list in lists)
{
var items = list.GetItems(CreateAllFilesQuery());
ctx.Load(items, icol => icol.Include(i => i.File));
results[list.Title] = items.Select( i=>i.File);
}
ctx.ExecuteQuery();
3) Prefer to load all files via CAML query as demonstrated below:
public static CamlQuery CreateAllFilesQuery()
{
var qry = new CamlQuery();
qry.ViewXml ="<View Scope=\"RecursiveAll\"><Query><Where><Eq><FieldRef Name=\"FSObjType\" /><Value Type=\"Integer\">0</Value></Eq></Where></Query></View>";
return qry;
}
Then the following example will return all the files in library:
var items = list.GetItems(CreateAllFilesQuery());
ctx.Load(items, icol => icol.Include(i => i.File));
ctx.ExecuteQuery();
var files = items.Select( i=>i.File).ToList();
It is more optimized way of loading specific lists from performance perspective
Complete example
How to load all files from document libraries using SharePoint CSOM:
using (var ctx = new ClientContext(webUri))
{
var results = new Dictionary<string, IEnumerable<File>>();
var lists = ctx.LoadQuery(ctx.Web.Lists.Where(l => l.BaseType == BaseType.DocumentLibrary));
ctx.ExecuteQuery();
foreach (var list in lists)
{
var items = list.GetItems(CreateAllFilesQuery());
ctx.Load(items, icol => icol.Include(i => i.File));
results[list.Title] = items.Select( i=>i.File);
}
ctx.ExecuteQuery();
//Print results
foreach (var result in results)
{
Console.WriteLine("List: {0}",result.Key);
foreach (var file in result.Value)
{
Console.WriteLine("File: {0}", file.Name);
}
}
}

foreach (List list in clientcontext.Web.Lists)
{
clientcontext.Load(list);
clientcontext.ExecuteQuery();
//list.TemplateFeatureId.ToString().Equals("") &&
string baseType = list.BaseType.ToString();
string listTitle = list.Title.ToString();
if (list.BaseType.ToString().Equals("DocumentLibrary", StringComparison.InvariantCultureIgnoreCase) && list.Title.ToString().Equals("TestLDOCS", StringComparison.InvariantCultureIgnoreCase))
{
foreach (Folder subFolder in list.RootFolder.Folders)
{
clientcontext.Load(subFolder.Files);
clientcontext.ExecuteQuery();
foreach (File f in subFolder.Files)
{
Console.WriteLine((string) f.Title);
}
}
}
}
}

Related

Is there a Sharepoint API to get nested directory structure for a particular folder at once within a site?

Is there a Sharepoint API to get entire content of files/folders for a particular folder at once ?
I don't want to use GetFolderByServerRelativePath recursively.
If don't want to use GetFolderByServerRelativePath, you can use CSOM with CAML Query like this:
string password = "pwd";
string account = "user#Tenant.onmicrosoft.com";
var secret = new SecureString();
foreach (char c in password)
{
secret.AppendChar(c);
}
using (ClientContext ctx = new ClientContext("https://Tenant.sharepoint.com/sites/sitename"))
{
ctx.Credentials = new SharePointOnlineCredentials(account,secret);
Web web = ctx.Web;
ctx.Load(web);
ctx.ExecuteQuery();
List list = web.Lists.GetByTitle("Documents");
ctx.Load(list);
ctx.ExecuteQuery();
Folder folder = web.GetFolderByServerRelativeUrl(web.ServerRelativeUrl + "/shared%20documents/");
ctx.Load(folder);
ctx.ExecuteQuery();
CamlQuery camlQuery = new CamlQuery();
camlQuery.ViewXml = #"<View Scope='RecursiveAll'>
<Query>
</Query>
</View>";
camlQuery.FolderServerRelativeUrl = folder.ServerRelativeUrl;
ListItemCollection listItems = list.GetItems(camlQuery);
ctx.Load(listItems);
ctx.ExecuteQuery();
foreach (var item in listItems)
{
Console.WriteLine(item.FieldValues["FileRef"].ToString());
}
}
will return all folders/subfolders/files together, hopefully, this is what you need.

How to change the content type of list items

Im trying to change the content type of all my Root Folders in a document library. I am not even sure if that is possible. When i run the code below i get the message that ListItem.ContentType is Writeprotected...
My Question is, is it possible to change the content type at all?
If yes how do i do it with CSOM?
Thanks
ContentType ct = list.ContentTypes.GetById("0x0120D520008AE499F0AEB1C647B9D6F0C9D3B7F9F100B56E2AEF9C715540BE5E87A04F54476E");
context.ExecuteQuery();
foreach (ListItem item in items)
{
context.Load(item, i => i.DisplayName);
context.Load(item, i => i.ContentType);
context.Load(ct, i => i.Id);
context.ExecuteQuery();
if (item.ContentType.Name == "Folder")
{
Console.WriteLine("Name: " + item.DisplayName + " ContentType:" + item.ContentType.Name);
if (item.ContentType.Sealed = true)
{
item.ContentType.Sealed = false;
item.Update();
context.ExecuteQuery();
}
item.ContentType = ct.Id;
item.Update();
context.ExecuteQuery();
}
}
Update the item content type like this, setting ContentTypeId field value:
List list = ctx.Web.Lists.GetByTitle("doc2");
ContentType ct = list.ContentTypes.GetById("0x0120001D61DFC51D574148B41D5DEB19779D19000C2B25DED7B1C34BB491C5BE59765450");
ctx.Load(ct);
ctx.ExecuteQuery();
CamlQuery caml = new CamlQuery();
ListItemCollection items = list.GetItems(caml);
ctx.Load(items);
ctx.ExecuteQuery();
foreach (ListItem item in items)
{
ctx.Load(item, i => i.DisplayName);
ctx.Load(item, i => i.ContentType);
ctx.Load(ct, i => i.Id);
ctx.ExecuteQuery();
if (item.ContentType.Name == "Folder")
{
item["ContentTypeId"] = ct.Id.ToString();
item.Update();
ctx.ExecuteQuery();
}
}

SharePoint Microsoft.SharePoint.Client.CamlQuery recursively return folders only (including subfolders)

I am attempting to pull back all the Folders and SubFolders (there can be any number) from a SharePoint site. I don't want the files (there could be thousands), so I am basically trying to just build a folder hierarchy. Additionally I only want the User created folders and the main "Documents" folders, not all the system ones.
That said, I found the following example that I though should have worked, but when I reduce it to just the folders I only get the top level folders:
https://stackoverflow.com/questions/16652288/sharepoint-client-get-all-folders-recursively
Here is the state of the current code. I am probably just missing something on the load (like an expresssion?):
public static void LoadContent(Microsoft.SharePoint.Client.Web web, out Dictionary<string, IEnumerable<Microsoft.SharePoint.Client.Folder>> listsFolders)
{
listsFolders = new Dictionary<string, IEnumerable<Microsoft.SharePoint.Client.Folder>>();
var listsItems = new Dictionary<string, IEnumerable<Microsoft.SharePoint.Client.ListItem>>();
var ctx = web.Context;
var lists = ctx.LoadQuery(web.Lists.Where(l => l.BaseType == Microsoft.SharePoint.Client.BaseType.DocumentLibrary));
ctx.ExecuteQuery();
foreach (var list in lists)
{
var items = list.GetItems(Microsoft.SharePoint.Client.CamlQuery.CreateAllFoldersQuery());
ctx.Load(items);
listsItems[list.Title] = items;
}
ctx.ExecuteQuery();
foreach (var listItems in listsItems)
{
listsFolders[listItems.Key] = listItems.Value.Where(i => i.FileSystemObjectType == Microsoft.SharePoint.Client.FileSystemObjectType.Folder).Select(i => i.Folder);
}
}
UPDATE
Just to help out anyone else who might just want the main folders and subfolders as a list of urls, here is the final code. I suspect it could be simplified but it is working. The trick after the help below was to get the "root" folder paths, which required a separate query. I think that is where it could prove easier to just get Folders -> Subfolders, but I have Folders -> Subfolders -> Subfolders and this solution gets that last subfolder, along with the root folders.
public static void LoadContent(Microsoft.SharePoint.Client.Web web, List<String> foldersList)
{
Dictionary<string, IEnumerable<Folder>> listsFolders = new Dictionary<string, IEnumerable<Folder>>();
var listsItems = new Dictionary<string, IEnumerable<ListItem>>();
var ctx = web.Context;
var lists = ctx.LoadQuery(web.Lists.Include(l => l.Title).Where(l => l.BaseType == BaseType.DocumentLibrary && !l.Hidden && !l.IsCatalog && !l.IsSiteAssetsLibrary));
ctx.ExecuteQuery();
foreach (var list in lists)
{
ctx.Load(list.RootFolder);
ctx.ExecuteQuery();
}
foreach (var list in lists)
{
if (list.Title != "Form Templates" && list.Title != "MicroFeed" && list.Title != "Site Assets" && list.Title != "Site Pages")
{
foldersList.Add(list.RootFolder.ServerRelativeUrl);
var items = list.GetItems(CamlQuery.CreateAllFoldersQuery());
ctx.Load(items, icol => icol.Include(i => i.FileSystemObjectType, i => i.Folder));
listsItems[list.Title] = items;
}
}
ctx.ExecuteQuery();
foreach (var listItems in listsItems)
{
listsFolders[listItems.Key] = listItems.Value.Where(i => i.FileSystemObjectType == FileSystemObjectType.Folder).Select(i => i.Folder);
}
foreach (var item in listsFolders)
{
IEnumerable<Folder> folders = item.Value;
foreach (Folder folder in folders)
{
foldersList.Add(folder.ServerRelativeUrl);
}
}
}
An example of what this returns:
1) In the provided example, to return Folder object, it needs to be explicitly included otherwise the exception occur, so replace the line:
ctx.Load(items);
with:
ctx.Load(items, icol => icol.Include(i => i.FileSystemObjectType, i => i.Folder));
2) "system" libraries could be excluded like this:
var lists = ctx.LoadQuery(web.Lists.Where(l => !l.Hidden && !l.IsCatalog && !l.IsSiteAssetsLibrary));
Modified example
public static void LoadContent(Web web, out Dictionary<string, IEnumerable<Folder>> listsFolders)
{
listsFolders = new Dictionary<string, IEnumerable<Folder>>();
var listsItems = new Dictionary<string, IEnumerable<ListItem>>();
var ctx = web.Context;
var lists = ctx.LoadQuery(web.Lists.Include(l =>l.Title).Where(l => l.BaseType == BaseType.DocumentLibrary && !l.Hidden && !l.IsCatalog && !l.IsSiteAssetsLibrary));
ctx.ExecuteQuery();
foreach (var list in lists)
{
var items = list.GetItems(CamlQuery.CreateAllFoldersQuery());
ctx.Load(items, icol => icol.Include(i => i.FileSystemObjectType, i => i.Folder));
listsItems[list.Title] = items;
}
ctx.ExecuteQuery();
foreach (var listItems in listsItems)
{
listsFolders[listItems.Key] = listItems.Value.Where(i => i.FileSystemObjectType == FileSystemObjectType.Folder).Select(i => i.Folder);
}
}
Try this.
var lists = ctx.LoadQuery(ctx.Web.Lists.Where(l => l.BaseType == BaseType.DocumentLibrary));
ctx.ExecuteQuery();
foreach (var list in lists)
{
Console.WriteLine(list.Title);
ListItemCollection listitems = list.GetItems(CamlQuery.CreateAllFoldersQuery());
ctx.Load(listitems, items => items.Include(item => item.Id,item=>item.Folder));
ctx.ExecuteQuery();
foreach (var item in listitems)
{
Console.WriteLine(item.Folder.ServerRelativeUrl);
}
}

Safely return all lists in site collection that match criteria

I'm using the following code to get all "Announcement" lists in a Web Applications site collection.
Unfortunately, sometimes the current user does not have permission to that site and the page fails with an exception, even inside the try block.
What would be the right way to do the following safely for all users, where even an anonymous user would just get no results?
static public List<SPListMeta> AllSiteAnnouncementsLists()
{
var returnList = new List<SPListMeta>();
foreach (SPSite oSiteCollection in SPContext.Current.Web.Site.WebApplication.Sites)
{
var collWebs = oSiteCollection.AllWebs;
try
{
foreach (SPWeb oWebsite in collWebs)
{
using (oWebsite)
{
var collSiteLists = oWebsite.GetListsOfType(SPBaseType.GenericList);
returnList.AddRange(from SPList oList in collSiteLists where oList.Title == "Announcements" select new SPListMeta(oList));
}
}
}
catch
{
}
}
return returnList;
}
Try give your code the right permission to execute.
using Microsoft.Sharepoint.Administrator;
SPSecurity.RunWithElevatedPrivileges(delegate(){
// Your source code goes here
});
To get all items of the specific list type from the site collection you have to use SPSiteDataQuery. Each user will get only those items they have permissions.
SPWeb web = SPContext.Current.Web;
SPSiteDataQuery query = new SPSiteDataQuery();
//Ask for all lists created from the announcement template.
query.Lists = "<Lists ServerTemplate=\"104\" />";
// Get the Title field. Define here all you need.
query.ViewFields = "<FieldRef Name=\"Title\" />";
// Set the sort order.
query.Query = "<OrderBy>" +
"<FieldRef Name=\"Title\" />" +
"</OrderBy>";
// Query all Web sites in this site collection.
query.Webs = "<Webs Scope=\"SiteCollection\" />";
DataTable dt = web.GetSiteData(query);
DataView dv = new DataView(dt);
This is what ended up working for me, though I do not know if it is the best way to do this.
static public List<SPListMeta> AllSiteAnnouncementsLists()
{
var returnList = new List<SPListMeta>();
var collWebs = SPContext.Current.Web.Site.WebApplication.Sites[0].OpenWeb().GetSubwebsForCurrentUser();
if(SPContext.Current.Site.RootWeb.DoesUserHavePermissions(SPBasePermissions.Open))
{
var collSiteLists = SPContext.Current.Site.RootWeb.GetListsOfType(SPBaseType.GenericList);
returnList.AddRange(from SPList oList in collSiteLists
where oList.DoesUserHavePermissions(SPBasePermissions.ViewListItems)
&& oList.BaseTemplate == SPListTemplateType.Announcements
select new SPListMeta(oList));
}
foreach (SPWeb oWebsite in collWebs)
{
returnList.AddRange(WebRecursion.GetListsForCurrentWeb(oWebsite, SPListTemplateType.Announcements));
foreach (SPWeb oSubSite in oWebsite.Webs)
{
returnList.AddRange(WebRecursion.GetListsForCurrentWeb(oSubSite, SPListTemplateType.Announcements));
}
}
return returnList;
}
public static List<SPListMeta> GetListsForCurrentWeb(SPWeb oWebsite, SPListTemplateType type)
{
var returnList = new List<SPListMeta>();
if (oWebsite.DoesUserHavePermissions(SPBasePermissions.Open))
{
using (oWebsite)
{
var collSiteLists = oWebsite.Lists;
returnList.AddRange(from SPList oList in collSiteLists
where oList.DoesUserHavePermissions(SPBasePermissions.ViewListItems)
&& oList.BaseTemplate == type
select new SPListMeta(oList));
}
}
return returnList;
}

Sharepoint: How to upload files with metadata including Taxonomy fields through web services

Being very new to SharePoint coding I have been assigned the task to create a prototype code to upload a file and setting the field values for that file that will show up when opening the sharepoint page with the file.
This has to be done from a remote machine and not the Sharepoint server itself so using the .Net objects for Sharepoint is out the question.
I quickly found out how to upload a file through the Sharepoint Web Service Copy.asmx:
void UploadTestFile() {
var file = #"C:\Temp\TestFile.doc";
string destinationUrl = "http://mysharepointserver/Documents/"
+ Path.GetFileName(file);
string[] destinationUrls = { destinationUrl };
var CopyWS = new Copy.Copy();
CopyWS.UseDefaultCredentials = true;
CopyWS.Url = "http://mysharepointserver/_vti_bin/copy.asmx";
CopyResult[] result;
byte[] data = File.ReadAllBytes(file);
FieldInformation mf1 = new FieldInformation {
DisplayName = "title",
InternalName = "title",
Type = FieldType.Text,
Value = "Dummy text"
};
FieldInformation mf2 = new FieldInformation {
DisplayName = "MyTermSet",
InternalName = "MyTermSet",
Type = FieldType.Note,
Value = "Test; Unit;"
};
CopyWS.CopyIntoItems(
"+",
destinationUrls,
new FieldInformation[] { mf1, mf2 },
data,
out result);
}
This code easily uploads any file to the target site but only fills the "title" field with info. The field MyTermSet in which I have added 3 terms allready - Test, Unit and Page - will not update with the values "Test;" and "Unit;".
Being very new to Sharepoint and me not grasping all the basics googling has told me that updating "File", "Computed" or "Lookup" fields does not work with the CopyIntoItems method, and MyTermSet being a Taxonomy field is - if I am correct - a Lookup field.
So how do I get MyTermSet updated with the values "Test;" and "Unit;" ?
I would really prefer If someone has a sample code on this. I have followed several hint-links but I am none the wiser. I have found no sample-code on this at all.
Have anyone made one single method that wraps it all? Or another method that takes in the destinationUrl from the file upload and updates the Term Set/Taxonomy field.
Puzzling together what I have found so far, I am now able to do as I wanted. But I would really like to be able to get the Taxonomy field GUIDs dynamically and NOT having to explicitly set them myself:
void UploadTestFile(string FileName, string DocLib, Dictionary<string, string> Fields = null) {
//Upload the file to the target Sharepoint doc lib
string destinationUrl = DocLib + Path.GetFileName(FileName);
string[] destinationUrls = { destinationUrl };
var CopyWS = new Copy.Copy();
CopyWS.UseDefaultCredentials = true;
CopyWS.Url = new Uri(new Uri(DocLib), "/_vti_bin/copy.asmx").ToString();
CopyResult[] result;
var data = File.ReadAllBytes(FileName);
CopyWS.CopyIntoItems(
"+",
destinationUrls,
new FieldInformation[0],
data,
out result);
if (Fields == null) return; //Done uploading
//Get the ID and metadata information of the fields
var list = new ListsWS.Lists();
list.UseDefaultCredentials = true;
var localpath = new Uri(DocLib).LocalPath.TrimEnd('/');
var site = localpath.Substring(0, localpath.LastIndexOf("/")); //Get the site of the URL
list.Url = new Uri(new Uri(DocLib), site + "/_vti_bin/lists.asmx").ToString(); //Lists on the right site
FieldInformation[] fiOut;
byte[] filedata;
var get = CopyWS.GetItem(destinationUrl, out fiOut, out filedata);
if (data.Length != filedata.Length) throw new Exception("Failed on uploading the document.");
//Dictionary on name and display name
var fieldInfos = fiOut.ToDictionary(x => x.InternalName, x => x);
var fieldInfosByName = new Dictionary<string, FieldInformation>();
foreach (var item in fiOut) {
if (!fieldInfosByName.ContainsKey(item.DisplayName)) {
fieldInfosByName.Add(item.DisplayName, item);
}
}
//Update the document with fielddata - this one can be extended for more than Text and Note fields.
if (!fieldInfos.ContainsKey("ID")) throw new Exception("Could not get the ID of the upload.");
var ID = fieldInfos["ID"].Value; //The ID of the document we just uploaded
XDocument doc = new XDocument(); //Creating XML with updates we need
doc.Add(XElement.Parse("<Batch OnError='Continue' ListVersion='1' ViewName=''/>"));
doc.Element("Batch").Add(XElement.Parse("<Method ID='1' Cmd='Update'/>"));
var methNode = doc.Element("Batch").Element("Method");
//Add ID
var fNode = new XElement("Field");
fNode.SetAttributeValue("Name", "ID");
fNode.Value = ID;
methNode.Add(fNode);
//Loop each field and add each Field
foreach (var field in Fields) {
//Get the field object from name or display name
FieldInformation fi = null;
if (fieldInfos.ContainsKey(field.Key)) {
fi = fieldInfos[field.Key];
}
else if (fieldInfosByName.ContainsKey(field.Key)) {
fi = fieldInfosByName[field.Key];
}
if (fi != null) {
//Fix for taxonomy fields - find the correct field to update
if (fi.Type == FieldType.Invalid && fieldInfos.ContainsKey(field.Key + "TaxHTField0")) {
fi = fieldInfos[field.Key + "TaxHTField0"];
}
else if (fi.Type == FieldType.Invalid && fieldInfosByName.ContainsKey(field.Key + "_0")) {
fi = fieldInfosByName[field.Key + "_0"];
}
fNode = new XElement("Field");
fNode.SetAttributeValue("Name", fi.InternalName);
switch (fi.Type) {
case FieldType.Lookup:
fNode.Value = "-1;#" + field.Value;
break;
case FieldType.Choice:
case FieldType.Text:
fNode.Value = field.Value;
break;
case FieldType.Note: //TermSet's
var termsetval = "";
var terms = field.Value.Split(';');
foreach (var term in terms) {
termsetval += "-1;#" + term + ";";
}
fNode.Value = termsetval.TrimEnd(';');
break;
default:
//..Unhandled type. Implement if needed.
break;
}
methNode.Add(fNode); //Adds the field to the XML
}
else {
//Field does not exist. No use in uploading.
}
}
//Gets the listname (not sure if it is the full path or just the folder name)
var listname = new Uri(DocLib).LocalPath;
var listcol = list.GetListCollection(); //Get the lists of the site
listname = (from XmlNode x
in listcol.ChildNodes
where x.Attributes["DefaultViewUrl"].InnerText.StartsWith(listname, StringComparison.InvariantCultureIgnoreCase)
select x.Attributes["ID"].InnerText).DefaultIfEmpty(listname).First();
//Convert the XML to XmlNode and upload the data
var xmldoc = new XmlDocument();
xmldoc.LoadXml(doc.ToString());
list.UpdateListItems(listname, xmldoc.DocumentElement);
}
Then I call it like this:
var fields = new Dictionary<string, string>();
fields.Add("Test", "Dummy Text");
fields.Add("MrTermSet", "Page|a4ba29c1-3ed5-47e9-b43f-36bc59c0ea5c;Unit|4237dfbe-22a2-4d90-bd08-09f4a8dd0ada");
UploadTestFile(#"C:\Temp\TestFile2.doc", #"http://mysharepointserver/Documents/", fields);
I would however prefer to call it like this:
var fields = new Dictionary<string, string>();
fields.Add("Test", "Dummy Text");
fields.Add("MrTermSet", "Page;Unit");
UploadTestFile(#"C:\Temp\TestFile2.doc", #"http://mysharepointserver/Documents/", fields);

Resources