Wednesday, July 3, 2013

SharePoint Client Object model - Observations and limitiations

best practices

SharePoint Client object models provide an object-oriented system for interoperating with SharePoint data from a remote computer or from the browser. They use underlying SharePoint web services to interact with SharePoint objects. SharePoint Client OM has provided access to a subset of the types and members of the SharePoint server-side object model which provides ability to perform most of the common scenarios. SharePoint Client object model (Javascript & Managed client OM) is a great way to communite with SharePoint data from outside of SharePoint but need to understand some of the limitations of the client OM.

  1. When you host the application that uses the CSOM outside the SharePoint server (web application outside SharePoint server which uses SharePoint Client OM) , then user credentials cannot be used until we use constraint delegation. It uses only the application pool identity of the web application because of double hop issue and not the actual user credentials.


  1. Update SharePoint list items using client OM from other web app outside SharePoint

It updates the modified information of the items with the App pool account and special handling is required to update the modified information with the actual logged in user



User userInfo = site.RootWeb.EnsureUser(HttpContext.Request.LogonUserIdentity.Name);

// Update Modified by Information

fileitem["Editor"] = userInfo;

fileitem.Update();



  1. Access SharePoint using client OM from other web app outside SharePoint

The SharePoint in build security cannot be used and only app pool identity is being used to verify the access against SharePoint objects. Client object model doesn’t support to check the permission of the list item for the current user before updating the same.

Server side code

DoesUserHavePermissions(SPBasePermissions) method is available in the following objects to verify the user’s permission

  • SPWeb

  • SPList

  • SPListItem

public bool DoesUserHavePermissions(

SPUser user,

SPBasePermissions permissionMask

)

It also has overrides to pass the SPUser to get the permission for that user. Managed client OM doesn’t have that.


Client OM code

In client OM Microsoft.SharePoint.Client , the DoesUserHavePermissions method is available only at the Web level and not at the ListItem & List level. Custom code required to check the permissions for the user.

Below code checks the permission for the APP POOL account in case of web application hosted in a different server and not the current Logged in user.

private static bool DoesUserHasPermission(ClientContext context, ListItem listItem, PermissionKind permKind)

{


// Load the permissions before checking

context.Load(listItem, t => t.EffectiveBasePermissions);

context.ExecuteQuery();


return listItem.EffectiveBasePermissions.Has(permKind);


}





  1. SPUtility.CreateISO8601DateTimeFromSystemDateTime (); equivalent in SharePoint Client Object Model (CSOM). This is being used in CAML query and need to filter some data fetched from list based on Date.



Server side code:

string dateString = Microsoft.SharePoint.Utilities.SPUtility.CreateISO8601DateTimeFromSystemDateTime(DateTime.Now);



Client OM:

Format the string using string formatter

string dateString = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ssZ");



  1. Performance issue

  • Every time when you need to read the property, the object on which the property belongs needs to be executed. Multiple round trips to server which impacts performance

In the below code, targetListItem.File needs to be executed to read a property. This needs to be done to all items in the collection.

ListItemCollection collPageListItem = pagesList.GetItems(camlQuery);

// Load the List item collection and execute

clientContext.Load(collPageListItem);

clientContext.ExecuteQuery();


foreach (ListItem targetListItem in collPageListItem)

{

// TO Read the file Property server relative URL, NEED TO excute targetListItem.File Object

clientContext.Load(targetListItem.File);

clientContext.ExecuteQuery();


SpPagesMetadata pagesItem = new SpPagesMetadata();

pagesItem.PageId = targetListItem.Id;


// TO Read the file Property server relative URL, NEED TO execute targetListItem.File Object

pagesItem.PageUrl = targetListItem.File.ServerRelativeUrl;

}



Optimized approach,

Load all the required values in the context and ExecuteQuery once which reduces round trips to the server

// Load the item and it's file at one time

clientContext.Load(collPageListItem);

clientContext.Load(collPageListItem, items => items.Include(item =>item.File.ServerRelativeUrl));

clientContext.ExecuteQuery();



Load only the required values E.g In the above code, we use only the ServerRelativeUrl property from the file object and hence load that alone.



  1. Differences in SPWeb and Web objects

SPWeb object has some differences to get the SPweb context. In server side code, we can create the instance of SPWeb without passing the exact web url.



Server side code:

SPWeb spWeb = spSite.OpenWeb(”/staffing/Pages/default.aspx”, false)

Second boolean value that specifies whether the exact URL must be supplied. If it’s false, it will handle itself.



Client OM code:

Microsoft.SharePoint.Client.Web web = site.OpenWeb(”/staffing”);



Need to provide only the web site relative url. No second parameter to handle the above case.

  1. SPSiteDataQuery Not available with client OM



Unlike the server side code, Site data query is not available in the Client OM and hence search SharePoint across multiple sub sites with a single query is not possible. Need to loop thru each sub site to perform search.



  1. SPRoleAssignmentCollection



To remove all role assignment i.e. to remove a permission from an list item in SharePoint is straight forward with server side code and whereas in client managed object model, it’s little different. Also, we can’t remove the permission without knowing the SharePoint group or user.



Server code

SPSecurity.RunWithElevatedPrivileges(delegate()

{

listItem.BreakRoleInheritance(true);

while (listItem.RoleAssignments.Count > 1)

{

listItem.RoleAssignments.Remove(0); // Index

listItem.RoleAssignments.RemoveById(0); // based on ID


}

});



Client OM code

To remove the role Assignment using client object model, following code will be used.

//Delete the Permission for the user or Group

fileitem.RoleAssignments.GetByPrincipal(rcmGroupInfo).DeleteObject();



clientContext.ExecuteQuery();





  1. Elevated privileges

RunWithElevatedPrivileges is not available in the client object model and hence user context cannot be elevated to run the code.

  1. To download a file from document library

Server side code

Using SpFile.OpenBinaryStream method

using (SPWeb web = site.OpenWeb())

{

SPFile file = web.GetFile(“/documents/testdocument.docx”);

System.IO.Stream strm = file.OpenBinaryStream();

}



Client code

Client OM doesn’t have the method with File object and hence need to use FileInformation class.

FileInformation has OpenBinaryDirect method to get the stream without executing the query.

ClientContext clientContext = new ClientContext("http://SpSiteUrl");

Site site = clientContext.Site;


// Execute the query before getting the stream to create the context

clientContext.ExecuteQuery();


// Get the file stream, This directly calls and get the file stream. Need NOT call Excecute query

FileInformation fileInfo = Microsoft.SharePoint.Client.File.OpenBinaryDirect(clientContext, "/Documents/ Technical%20Design.docx");


// Access the stream for further processin

System.IO.Stream stream = fileInfo.Stream;

2 comments:

Anonymous said...

Hi, I am using below code to get list item properties in javascript.

when we select list item in sharepoint list. It will call below function internally to get properties of targetListItem. Some times i am getting uniqueItemID and documentGuid null. If i click second time it is loading uniqueItemID and documentGuid property. Can you please help regarding this

var Listitem = selectedItems[0].id;
var targetList = clientContext.get_web().get_lists().getById(listId);
targetListItem = targetList.getItemById(Listitem);
clientContext.load(targetListItem);

function onQuerySucceeded(sender, args) {
try {
var listItemInfo = '';


uniqueItemID = targetListItem.get_item('UniqueId');
documentGuid = targetListItem.get_item('DocumentGUID');


}
catch (error) {


}

Anonymous said...

Very helpful post. I was able to use it successfully. Thanks.