01 March 2011

Sharepoint Client Object Model: sites, roles, lists, documents, AD users and permissions

Recently I had my very first encounter with Sharepoint, and I cannot say it was a pleasant meeting. I was doing a proof of concept for a customer to manipulate Sharepoint from code using the new Sharepoint 2010 Client Object Model. Not that this is a particular unpleasant job in itself and neither is the API particulary bad (although definitely odd), but the MSDN documentation about the Client Object Model is far from self-explanatory, and lacks the – for me, at last –  most crucial part of educational material: simple how-to examples.

Most examples found elsewhere are either based on older API’s, incomplete and sometimes completely wrong. And you have to roam about half the internet to get pieces of code together. So I thought it would be a good idea to cobble together pieces from the POC into a comprehensive blog post with what I’ve learned, hoping to save other people the same quest.

Be aware that I learned this all just in the past few days so things are pretty crude at times. I am by no means a Sharepoint wizard - nor am I planning on becoming one :-).  The idea is just to show how it’s done. I think there are a lot of people out there who know how to do things better and more efficient than me: it just they don’t blog about it ;-)

This code was used to talk to a Sharepoint foundation on a domain controller outside the domain on which I actually ran the code.

Things covered in this post

  • Create a site
  • Retrieve a site by title
  • Retrieve a role by Role type
  • Retrieve a Sharepoint principal by Active Directory user or group name
  • Retrieve a document library by name
  • Retrieve a document library template by name
  • Create a document library and set permissions
  • Create a folder in a document library
  • Upload a file to a document library
  • Download a file from a document library
  • List all folders or files in a document library

Setting the stage

It started out making a “Sharepointhelper” class. The basics of this thing is a follows:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Security.Principal;
using System.Text;
using Microsoft.SharePoint.Client;
using Microsoft.SharePoint.Client.Utilities;
using File = Microsoft.SharePoint.Client.File;

namespace TestSharepoint
{
  public class SharepointHelper
  {
    private ClientContext clientContext;
    private Web rootWeb;

    public SharepointHelper(string url, string username, string password)
    {
      clientContext = new ClientContext(url);
      var credentials = new NetworkCredential(username, password, "domain");
      clientContext.Credentials = credentials;
      rootWeb = clientContext.Web;
      clientContext.Load(rootWeb);
    }
  }
}

Creating the object should go like this:

var sh = new SharepointHelper("http://yourserver", "aUser", "hisPassword");

aUser must be a user with enough rights to perform sharepoint administration. I used the domain admin username and password and I assure you – that works ;-)

Create a site

public void CreateSite(string siteDescription, string siteTitle, string siteUrl)
{
  rootWeb = rootWeb.Webs.Add(new WebCreationInformation
    {
      Description = siteDescription,
      Title = siteTitle,
      Url = siteUrl,
      UseSamePermissionsAsParentSite = false
    });
  clientContext.ExecuteQuery();
}
Usage sample: 
sh.Create("My site description", "MySite", "mysiteurl");

Retrieve a site by title

public Web GetWebByTitle(string siteTitle)
{
  var query = clientContext.LoadQuery(
    rootWeb.Webs.Where(p => p.Title == siteTitle));
  clientContext.ExecuteQuery();
  return query.FirstOrDefault();
}
Usage sample: 
var w = sh.GetWebByTitle("MySite");

Retrieve role by Role type

private RoleDefinition GetRole(string siteTitle, RoleType rType)
{
  var web = GetWebByTitle(siteTitle);
  if (web != null)
  {
    var roleDefs = web.RoleDefinitions;
    var query = clientContext.LoadQuery(
        roleDefs.Where(p => p.RoleTypeKind == rType));
    clientContext.ExecuteQuery();
    return query.FirstOrDefault();
  }
  return null;
}
Usage sample: 
var r = sh.GetRole("MySite", RoleType.Contributor);
will get you the contributor role.

Retrieve a Sharepoint principal by Active Directory user or group name

Now this one took me a very long time. For some reason there is a static Utility.SearchPrincipals method that gets you a PrincipalInfo object, but you can never get to get a Principal object that you can use for setting permissions. I spent a long time scratching my head how to get around this before I found there is another way:
public Principal GetPrincipal(string name)
{
  if (web != null)
  {
    try
    {
      var principal = web.EnsureUser(name);
      clientContext.Load(principal);
      clientContext.ExecuteQuery();
      if (principal != null)
      {
        return principal;
      }
    }
    catch (ServerException){}
  }
  return null;
}
Usage sample: 
var g = sh.GetPrincipal("MyUserGroup");
var u = sh.GetPrincipal("MyUser");

This will, as you can see, get you either a user group’s principal or a single user’s principal. Since I was looking for a group’s principal it never occurred to me to try the “EnsureUser” method. If you don’t know what a Principal is – neither do I (at least not exactly) but I of think it as a descriptor of a user’s or group's credentials.

Retrieve a document library by name

public List GetDocumentLibrary(string siteTitle, string libraryName)
{
  var web = GetWebByTitle(siteTitle);
  if (web != null)
  {
    var query = clientContext.LoadQuery(
         web.Lists.Where(p => p.Title == libraryName));
    clientContext.ExecuteQuery();
    return query.FirstOrDefault();
  }
  return null;
}
Usage sample: 
var g = GetDocumentLibrary("MySite", "myDocumentLibrary");

Retrieve a document library template by name

public ListTemplate GetDocumentLibraryTemplate(Web web, string name)
{
  ListTemplateCollection ltc = web.ListTemplates;
  var listTemplates = clientContext.LoadQuery(
    ltc.Where(p => p.InternalName == name));
  clientContext.Load(ltc);
  clientContext.ExecuteQuery();
  return listTemplates.FirstOrDefault();
}
Usage sample: 
var t = sh.GetDocumentLibraryTemplate(web, "doclib");
This will get you the template for the document library type.

Create a document library and set permissions

Now this was what I actually had to prove in the POC, and you can see this as it uses a lot of the previous samples:

public bool CreateDocumentLibrary(string siteTitle, string libraryName, 
                                  string libraryDescription, string userGroup)
{
  var web = GetWebByTitle(siteTitle);
  if (web != null)
  {
    // First load all the list
    var lists = web.Lists;
    clientContext.Load(lists);
    clientContext.ExecuteQuery();

    // Create new lib based upon the doclib template
    var newList = lists.Add(new ListCreationInformation
      {
        Title = libraryName,
        Description = libraryDescription,
        TemplateType = 
          GetDocumentLibraryTemplate(web, "doclib").ListTemplateTypeKind
      });
    clientContext.ExecuteQuery();

    // Override default permission inheritance
    newList.BreakRoleInheritance(true, false);
    // Get principal for usergroup and the contributor role
    var principal = GetPrincipal(userGroup);
    var role = GetRole(siteTitle, RoleType.Contributor);
    
    // Add the role to the collection.
    var collRdb = new RoleDefinitionBindingCollection(clientContext) {role};
    var collRoleAssign = newList.RoleAssignments;
    collRoleAssign.Add(principal, collRdb);

    clientContext.ExecuteQuery();
    
    return true;
  }
  return false;
}
Usage sample: 
var result = sh.CreateDocumentLibrary("MySite", "myDocumentLibrary",
                                      "A very nice library", "MyUserGroup");

Which will create a document library “myDocumentLibrary” in “MySite” with a contributor role for “MyUserGroup”. Like I said, it’s pretty crude still here and there, but you get the idea

Create a folder in a document library

public void CreateFolder( string siteTitle, string libraryName, string folder)
{
  var list = GetDocumentLibrary(siteTitle, libraryName);
  if (list != null)
  {
    var folders = list.RootFolder.Folders;
    clientContext.Load(folders);
    clientContext.ExecuteQuery();
    var newFolder = folders.Add(folder);
    clientContext.ExecuteQuery();
  }
}
I'll skip the usage sample here, as I suppose it's pretty self-explanatory now.

Upload a file to a document library

public void UploadDocument( string siteTitle, string libraryName, string fileName )
{
  var web = GetWebByTitle(siteTitle);
  var fInfo = new FileInfo(fileName);
  var targetLocation = string.Format("{0}/{1}/{2}", web.ServerRelativeUrl, 
     libraryName, fInfo.Name);
  
  using (var fs = new FileStream(fileName, FileMode.Open))
  {
    File.SaveBinaryDirect(clientContext, targetLocation, fs, true);
  }
}
Usage sample: 
var result = sh.UploadDocument("MySite", "myDocumentLibrary",
                              @"c:\temp\sample.png");
This will upload the file c:\temp\sample.png as "sample.png" in the "myDocumentLibrary" library.

Download a file from a document library

What goes up must come down, eh? This one is a bit odd, as it strips the directory name of the target file and tries to find the file in Sharepoint with it, but it works, so what:

public void DownloadDocument(string siteTitle, string libraryName, 
                             string fileName)
{
  var web = GetWebByTitle(siteTitle);
  var fInfo = new FileInfo(fileName);

  var source = string.Format("{0}/{1}/{2}", web.ServerRelativeUrl, 
                                            libraryName, fInfo.Name);
  var spFileInfo = File.OpenBinaryDirect(clientContext, source);
  using (var fs = new FileStream(fileName, FileMode.OpenOrCreate))
  {
    spFileInfo.Stream.CopyTo(fs);
  }
}
Usage sample: 
sh.DownloadDocument("MySite", "myDocumentLibrary",
                     @"c:\temp\sample.png");
will search for the file "sample.png" in "myDocumentLibrary" and try to download that to c:\temp

Listing files or folders in a document library

And finally:

public List<File> ListFiles(string siteTitle, string libraryName)
{
  var list = GetDocumentLibrary(siteTitle, libraryName);
  var files = list.RootFolder.Files;
  clientContext.Load(files);
  clientContext.ExecuteQuery();
  return files.ToList();
}

public List<Folder> ListFolders(string siteTitle, string libraryName)
{
  var list = GetDocumentLibrary(siteTitle, libraryName);
  var folders = list.RootFolder.Folders;
  clientContext.Load(folders);
  clientContext.ExecuteQuery();
  return folders.ToList();
}

and if you don’t mind, I’ll skip the usage samples here as well.

27 comments:

Martin said...

Hi Joost

Thanks a lot for this good example. Have you ever tried to get the changes of a specified Document Library on SP 2010 with Client OM?

Example: Dept A has a folder that has to be synced with their local computers.

// client Context is already initialized and working

var syncFolder = this.clientContext.Web.GetFolderByServerRelativeUrl("/deptA/Shared Documents/DocumentsA");
=> no changes available on syncFolder

var syncList = this.clientContext.Web.Lists.GetByTitle("Shared Documents");
var changes = syncList.GetChanges(new ChangeQuery(true, true));
=> no changes, empty list.. but there must be some

var siteChanges = this.clientContext.Web.GetChanges(new ChangeQuery(true, true));
=> no changes, empty list.. but there must be some

(of course the queries are loaded and executed afterwards - e.g. downloading a specified file is working)

Do you have other ideas how my task can be accomplished? It must be done with Client Object Model on SharePoint 2010 (on 2007 it was working with FileSyncFramework of Microsoft, done with UNC Paths to SP2007 Server)

Thanks in advance

Martin

Loc#alJoost said...

@Martin,

As I stated in my post, I am by no means a SharePoint guru - I just did a proof of concept and created a blog post because I was very frustrated by the lack of decent examples. What you see in this post is about all I know from SharePoint now. So now, I am afraid I have no idea how your problem can be solved. I suggest you blog it as you have found the answer ;-)

Driss EL FIGHA said...

What does mean ? I don't find any explanation for SharepointHelper.
var sh = new SharepointHelper("http://yourserver", "aUser", "hisPassword");

Joost van Schaik said...

@Driss, SharepointHelper is the class I created for my sample. See top of the post, very first piece of code.

nada said...

Hi Joost,
Like your article, I've since based a couple classes in my project on it.
However, I have some concerns about the ClientContext member variable.
Most Sharepoint documentation states that if you don't wrap the clientContext inside a using statement, or call Dispose() explicitly, that you WILL have memory leaks, because it implements IDisposable.
While the object scope should clean it up, have you had any memory issues?
Thoughts? Thanks in advance!

Joost van Schaik said...

@Nada, After creating this class I never did anything again with SharePoint, let alone this class. I am honored you actually used it succesfully ;-). If you suffer from memory leaks indeed, I'd suggest modifying my crude sample class to implement the IDisposabe pattern, and then dispose of the ClientContext member in the ususal way. And then blog about it yourself. I will add a link to your blog if you do ;-)

SiemprePensandoEnAlgo said...

Excelente, pasé un día entero buscando información sobre la mejor forma de acceder a Sharepoint desde un cliente, y en este post el amigo Joost no sólo ofrece ejemplos sino "de regalo" una clase helper con todo resuelto... ¡gracias Joost!

Khetienne said...

Hi - I have one question, at the top of your blog, you have "using" statements
i.e. using system;

Where do they go in the html?
- Under HTML, Head?
yes, I'm a newbie!

Joost van Schaik said...

@Khetienne: they do not go anywhere at all in the HTML. "using" in this context is c# only.

jayakrishnaakkiraju said...

Hey this is JK
its really a nice article....
Hope my blog will add few more points to share...
http://jayakrishnaakkiraju.blogspot.com/2011/11/sharepoint-2010.html

http://jayakrishnaakkiraju.blogspot.com/2011/11/sharepoint-2010-ecma-script-client-om.html

BanG said...

Hi Joost,

I tried ur code, Created an empty sharepoint project & added ur code there. And calling one of ur function in a command button & deployed successfully.

But when i click the button i get an exception,"(401) Unauthorized."

What am i supposed to do next.. Thank in advance.

Bang

Joost van Schaik said...

@Bang I really don't know unless you provide more code. But please be advised, I am not a SharePoint expert, and this article is actually describing *ALL* of my experience. I haven't touched SharePoint after it. I suggest you seek help from a SharePoint MVP.

Ase said...

Listing files or folders in a document library can you please tell me how t consume it? I'm planning to use this methods and club it in a wcf and invoke it in web application.

Regards
Kajal

Joost van Schaik said...

@Ase, I am afraid I am not a SharePoint specialist, this article describes my only ever foray into SharePoint. Try to contact a SharePoint MVP, maybe they'll be able to help you out

I.S.C. Román Pérez said...

As a contribution. If we need to implement the code for download a file from a document library, and we are using Framework before the 4.5 version, we need to create the CopyTo() method...

[CODE]
spFileInfo.Stream.CopyTo(fs);

Here is the solution.

http://weblogs.asp.net/gunnarpeipman/archive/2010/12/28/stream-copyto-extension-method.aspx

[CODE]
public static void CopyTo(this Stream fromStream, Stream toStream)
{
if (fromStream == null)
throw new ArgumentNullException("fromStream");
if (toStream == null)
throw new ArgumentNullException("toStream");

var bytes = new byte[8092];
int dataRead;
while ((dataRead = fromStream.Read(bytes, 0, bytes.Length)) > 0)
toStream.Write(bytes, 0, dataRead);
}

venkadesh s said...

Hi,

using client Object Model,Can we Get all the quota templates from SP 2010 Site Collection ??

Joost van Schaik said...

@venkadesh, dude, I haven't the slightest idea what you are talking about. This is my one and only development experience with sharepoint and that's over 2 years ago. Sorry, can't help you here - find a *real* sharepoint expert ;-)

Graeme Parkins said...

This was excellent and helped me ton! Thank you very much!

John Isley said...

Hello, thank you for putting this together. This got me moving in the right direction.

Joubert Sarte said...

Hi Joost,

How can I assign user/group permission to the site created?

Sorry, I'm just a newbie.

Thanks,

Joost van Schaik said...

@Joubert Sarte Sorry - this article was written during my one and only encounter with SharePoint development. I never touched it again after that (2.5+ years ago) really have not clue.

Ase said...

Yes it's very much possible I have seen numerous post on it in msdn blog for sharepoint development

santhosh Emmadi said...

how can we get the public url of file in share point using clientside object model in c#


Thanks In Advance.

Joost van Schaik said...

@santhosh like I wrote, I am not a SharePoint expert and this article is over 4 years old ;). I honestly have no idea. Sorry

David Serafim said...

I'm getting problems with the line:
return query.FirstOrDefault();

The error is: cannot be inferred from the using try specifying the type arguments explicity

Can anybody help?

Thanks

Sri Rao said...

I AM GETTING ERROR WHILE DOWNLOAD FILE WITH VERSION USING CSOM.

THERE IS NO OTHER WAY I FOUND SO FAR.
I TRIED
WEBSERVICES
WEBCLIENT
CSOM

SERVERSIDE I CANT USE BECAUSE I AM IMPLEMENTING THIS CODE AS EXE TOOL. SO THAT WILL RUN ON DESKTOP/PCS.

PLS REPLY ME...

THANKS,
VIKRAM

Joost van Schaik said...

Hey Vikram,

You may have noticed the article is 4.5 years old and I haven't been blogging any SharePoint related stuff since. At this point I don't even know whether or not this code actually works at all with more recent versions of SharePoint. So I suggest you look further on StackOverFlow or the like if this does not work for you.

Incidentally, YOU MIGHT LET GO OF THE CAPS LOCK KEY once in a while. ;)