Sometime, we need to go through all sub-site of site collection to do something, however, it's very easy if you are logged in by Designer or Full Control. Because these account have full rights on subsites and the problem happens with Contributor or Reader. Look at the code:
SPWeb parent = SPContext.Current.Web;
foreach (SPWeb web in parent.Webs)
{
try
{
table.Merge(this.GetDataFromURL(new Uri(web.Url), schemaName, itemLimit));
}
catch (Exception ex)
{
//logger.Warn("DataBind::", ex);
}
finally
{
web.Dispose();
}
}
The code runs without error but you will get the Access denied on the page and the stack trace shows full message like: "Unable to evaluate expression because the code is optimized or a native frame is on top of the call stack." That's very bad practice. Because of requirement, there are some subsites that Contributors or Readers do not have permission to read. However, we must get the data from remaining subsites which they have the right to read the data.
The code will change to:
SPWeb parent = SPContext.Current.Web;
parent.Site.CatchAccessDeniedException = false;
foreach (SPWeb web in parent.Webs)
{
try
{
table.Merge(this.GetDataFromURL(new Uri(web.Url), schemaName, itemLimit));
}
catch (Exception ex)
{
//logger.Warn("DataBind::", ex);
}
finally
{
web.Dispose();
}
}
We need to set property "CatchAccessDeniedException = false" for SPSite object. SharePoint will know that catch any site does have permission to read. As a result, you never get "Access denied" exception on page but you also never get data correctly, because the collection of Webs will throw exception if any subsite strict from user does not have permission.
The best practice for this issue is to get all subsites which Current user has the right to read, the code to change again:
SPWeb parent = SPContext.Current.Web;
parent.Site.CatchAccessDeniedException = false;
foreach (SPWeb web in parent.GetSubwebsForCurrentUser())
{
try
{
table.Merge(this.GetDataFromURL(new Uri(web.Url), schemaName, itemLimit));
}
catch (Exception ex)
{
//logger.Warn("DataBind::", ex);
}
finally
{
web.Dispose();
}
}
Method "GetSubwebsForCurrentUser()" of SPWeb just gets all subsites which current user is to be able to access, atleast, reading the data.