Sharepoint - Get the all sub sites from a site collection along with site hierarchy using CSOM
Here's a approach based of the Search which should be way faster and more ressource friendly.
It currently only groups subwebs after rootwebs, but could easily modified to also group the subwebs.
Result:
[ 22.04.2016 11:41:02 ] Infrastructure found by Search:
https://rootSPDomain.de
https://rootSPDomain.de
https://rootSPDomain.de/SubWeb1
https://rootSPDomain.de/SubWeb1/SubWeb1
https://rootSPDomain.de/SubWeb2
https://rootSPDomain.de/SubWeb2/SubWeb1
Code:
private static Dictionary<string, List<string>> GetAllWebsBySearch(ref int errorCount)
{
var webUrls = new Dictionary<string, List<string>>();
try
{
using (var ctx = new ClientContext(Settings.Default.SearchCenterUrl))
{
var sites = SearchWebs(ctx, Settings.Default.SearchQuery + " contentclass:STS_Site");
var webs = SearchWebs(ctx, Settings.Default.SearchQuery + " contentclass:STS_Web");
if (sites.Count == 0)
Log.ThrowCritical(string.Format("No sites found for searchstring '{0}'",
Settings.Default.SearchQuery));
foreach (var site in sites)
webUrls.Add(site, new List<string> { site });
foreach (var web in webs)
{
var parentSite = sites.Where(site => web.StartsWith(site)).OrderByDescending(s => s.Length).FirstOrDefault();
if (parentSite != null)
{
if (!webUrls[parentSite].Contains(web))
webUrls[parentSite].Add(web);
}
else
{
Log.Error(string.Format("Could not find site for web {0}", web));
}
}
}
}
catch (Exception ex)
{
Log.Error(string.Format("Error while searching webs at {0}",
Settings.Default.SearchCenterUrl));
Log.Error(string.Format(CultureInfo.InvariantCulture, "{0}", ex.Message));
errorCount++;
}
return webUrls;
}
private static List<string> SearchWebs(ClientContext ctx, string query)
{
var returnValue = new List<string>();
var rowLimit = 500;
var startRow = 0;
var allResultsFetched = false;
var fieldPath = "Path";
while (!allResultsFetched)
{
Log.Verbose(string.Format(CultureInfo.InvariantCulture, "Search with query - {0}", query));
var keywordQuery = new KeywordQuery(ctx)
{
QueryText = query,
RowLimit = rowLimit
};
keywordQuery.SelectProperties.Clear();
keywordQuery.SelectProperties.Add(fieldPath);
keywordQuery.StartRow = startRow;
keywordQuery.TrimDuplicates = false;
var searchExecutor = new SearchExecutor(ctx);
var results = searchExecutor.ExecuteQuery(keywordQuery);
ctx.ExecuteQuery();
foreach (var resultRow in results.Value[0].ResultRows)
{
var path = resultRow.Keys.Contains(fieldPath) && (resultRow[fieldPath] != null)
? resultRow[fieldPath].ToString()
: string.Empty;
returnValue.Add(path);
}
if (results.Value[0].RowCount < rowLimit)
allResultsFetched = true;
else
startRow = startRow + rowLimit + 1;
}
return returnValue;
}
private static void LogInfrastructure(Dictionary<string, List<string>> webInfrastructure)
{
var message = "Infrastructure found by Search:\n";
foreach (var site in webInfrastructure)
{
message += site.Key + "\n";
foreach (var web in site.Value)
message += "\t" + web + "\n";
message += "\n";
}
Log.Message(message);
}
Usage:
//get all web urls from the search
var webInfrastructure = GetAllWebsBySearch(ref errorCount);
LogInfrastructure(webInfrastructure);
Change allWebs
into a list containing level+Web
Add a int level
parameter to LoadAllWebs
In the first call pass 0 as level
.
In the recursive call inside LoadAllWebs
pass level+1
Per Jakobsen solution worked for me. Please find the below code for reference
using (var clientContext = new ClientContext(destinationSiteUrl))
{
clientContext.Credentials = new SharePointOnlineCredentials(UserName, secure);
DataTable dtsubsites = new DataTable();
Web oWebsite = clientContext.Web;
clientContext.Load(oWebsite.Webs);
clientContext.ExecuteQuery();
var listclass = new List<webdetails>();
var allWebs = new List<Web>();
var objallWebs = LoadAllWebs(clientContext.Site.RootWeb, listclass, 0);
GridView1.DataSource = objallWebs.ToList();
GridView1.DataBind();
}
public class webdetails
{
public string csomweb { get; set; }
public Int32 webhirarchy { get; set; }
}
public List<webdetails> LoadAllWebs(Web web, List<webdetails> allWebs,int level)
{
var ctx = web.Context;
ctx.Load(web);
ctx.Load(web.Webs);
ctx.ExecuteQuery();
// allWebs.Add(web);
allWebs.Add(new webdetails { csomweb = web.Title, webhirarchy = level });
foreach (var subWeb in web.Webs)
{
LoadAllWebs(subWeb, allWebs, level+1);
}
return allWebs;
}