I have mixed feelings about SharePoint, Microsoft’s flexible but infuriating collaboration platform. It makes difficult things easy and easy things difficult, or something like that. Today’s story is an example, and may also be of interest if you are wondering how to write code that manipulates documents in SharePoint as found in Office 365.
The problem started when some contacts of mine who use Office 365 could not open a folder in Windows Explorer. They received a permission error along with the famous invitation to “Contact your network administrator to request access.”
The folder in question is actually a SharePoint folder which accesses Office 365 through WebDAV. I took a look, and found that, whatever the problem was, it had nothing to do with permissions. I also observed that there was no problem accessing the folder through the web browser; but like many users, these people prefer to use Explorer.
Next, I started moving files out of the troublesome folder into another one. I began to suspect that some rogue document was causing the error. This suspicion proved correct, but it was not easy to track down. The problem: the SharePoint web user interface does not provide any bulk copy or move option. If you want to move a bunch of documents, the recommended way is to use Windows Explorer, the exact feature that in this instance was not working.
Moving documents one by one through a laborious Web UI is no fun, so I then had the bright idea of writing some code to move the documents. This means taking a dive into the labyrinthine SharePoint API.
I was surprised how hard this is. Here is how I got started:
First, I downloaded the SharePoint SDK and run the setup. I chose to install only the Foundation help and samples.
Next, I created a new Windows Forms project in Visual Studio 2010. Note you must set the project to target the full .NET Framework 4.0, not just the Client profile
After that, I had to copy two DLLs from my own SharePoint 2010 server. These are in:
C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\ISAPI
I am surprised they are not included with the SDK.
After that, I looked for some code samples for the SharePoint Client Object Model. You can find this described here or consult the reference here. It is a capable API, but you soon realise why there is plenty of work for SharePoint specialists. ClientContext, CAML queries, FolderServerRelativeUrl: there is a lot to get your head around.
The first problem I had though was authentication. Office 365 uses claims-based authentication, whereas all the SharePoint API examples seem to assume you are on an intranet and already authenticated for your SharePoint server. Coding for claims-based authentication is a headache.
I tried code from here to authenticate against the Office 365 claims-based federation server, but with no success. It seems to be based on beta code and does not work now. I then read the official document on the subject here and downloaded the sample code. Here is what worked for me:
Add the ClaimsAuth project from the sample to my Windows Forms solution.
Modify this line in ClaimsWebAuth.cs:
Application.Run(DisplayLoginForm);
to this
DisplayLoginForm.ShowDialog();
the reason being that ClaimsAuth is designed for a console application.
Then I could run some basic Client Object Model code like this:
ClientContext cc = ClaimClientContext.GetAuthenticatedContext(url);
if (cc != null)
{
cc.Load(cc.Web);
cc.ExecuteQuery();
lbTest.Text = "Title: " + cc.Web.Title;
cc.Dispose();
}
All this does is to connect to the Office 365 SharePoint site at url and display its title – but if you can do that, you have got past the first hurdle.
Next I had to figure out how to move all the documents in one folder to another. Again, I found this tricky. I was able to list all the items in a library, which is the top-level folder for a collection of documents, but how do you list all the items in a subfolder? Something to do with CAML, it seems, also known as Collaborative Application Markup Language. Does anyone out there love CAML? I thought not. CAML queries are like SQL queries chopped up into XML elements.
Another characteristic of the Client Object Model is that you constantly have to call the Load and ExecuteQuery methods of your ClientContext object, otherwise you will get a PropertyOrFieldNotInitializedException. There is a good reason for this, as it reduces the amount of data passing over the wire, but it can also be perplexing.
Here is the code I ended up with, where “docs” is the top-level folder or library name,
if (cc != null)
{
cc.Load(cc.Web);
cc.ExecuteQuery();
var site = cc.Web;var lib = site.Lists.GetByTitle("docs");
CamlQuery camlQuery = new CamlQuery();
camlQuery.FolderServerRelativeUrl = @"/docs/path/to/sourcefolder";string camlQueryXml = "<View>" +
"<Query>" +
"<OrderBy>" +
"<FieldRef Name=’FileLeafRef’ Ascending=’True’ />" +
"</OrderBy>" +
"</Query>" +
"</View>";
camlQuery.ViewXml = camlQueryXml;ListItemCollection lis = lib.GetItems(camlQuery);
cc.Load(lis);
cc.ExecuteQuery();
ListItem FolderCF = null;
foreach (ListItem li in lis)
{string sTitle = li.FieldValues["FileLeafRef"].ToString();
// you can inspect the title to see if you want to move the file,
//eg only those beginning with a letter in the first half of the alphabet
{
File thefile = li.File;cc.Load(thefile);
cc.ExecuteQuery();if (!li.File.ServerObjectIsNull.Value)
{
string dest = @"/docs/path/to/destfolder/" + thefile.Name;
thefile.MoveTo(dest, MoveOperations.Overwrite);
cc.ExecuteQuery();
}
}}
cc.Dispose();
}
Pretty simple? Maybe it is to a SharePoint guru; all I can say is that I did not find it intuitive.
Note that this is not intended as production code so if you borrow it please add exception handling etc. It was a quick hack to solve a problem.
The good news is that once I was able to move documents from folder to folder programmatically, I was able to troubleshoot the original problem. Mysteriously, there was one document which, if it was in a folder, caused the access denied error when opened with WebDAV. Once I isolated the document, I discovered that if I renamed it, the problem went away. Curiously, there are no special characters in the name, just letters and spaces, so this is something of a mystery.
Still, it was a useful exercise, especially since moving a batch of documents using the Client Object Model seems quicker than using Explorer and WebDAV.
I’ve also seen this kind of problem in situations not involving SharePoint or Explorer. Usually, you can track the problem down to a single character.
There are a lot of Unicode characters that resemble Latin characters but aren’t. My first guess would be that one of the spaces isn’t the space character that you expect.
well done Tim. SharePoint online work/dev/admin is hurdle after hurdle after hurdle… I wish I was working in an on-premise system again.