Working with the managed client object model
Because the managed client object model is supported by IntelliSense,
is checked at compile time, and functions synchronously, many
developers choose to develop apps that utilize remote webs and the
managed CSOM to communicate with SharePoint. Using the managed client object model is a simple matter of setting a reference to the assemblies Microsoft.SharePoint.Client.dll and Microsoft.SharePoint.ClientRuntime.dll, adding a using statement for the Microsoft.SharePoint.Client namespace, and writing code. This section details how to perform basic operations with the managed client object model.
When working with the client object models, you will quite often be interested in returning collections of items such as all the lists in a site or all of the items in a list. Collections of items can be returned by using either the Load or LoadQuery methods. When specifying the items of a collection to return, you can use the Load method along with a LINQ query formatted by using method syntax. Additionally, you can use the LoadQuery method with a LINQ query formatted by using query syntax. Example 2 shows how to return all of the list titles in a site for which the Title is not NULL.
Example 2. Returning collections by using LINQ
string appWebUrl = Page.Request["SPAppWebUrl"];
using (ClientContext ctx = new ClientContext(appWebUrl))
{
//Method Syntax
ctx.Load(ctx.Web,
w => w.Lists.Include(l => l.Title).Where(l => l.Title != null));
ctx.ExecuteQuery();
foreach (List list in ctx.Web.Lists)
{
Response.Write(list.Title);
}
//Query Syntax
var q = from l in ctx.Web.Lists
where l.Title != null
select l;
var r = ctx.LoadQuery(q);
ctx.ExecuteQuery();
Response.Write("<ul>");
foreach (var i in r)
{
Response.Write("<li>");
Response.Write(i.Title);
Response.Write("</li>");
}
Response.Write("</ul>");
}
Because of the disconnected nature of the client object model, error
handling is especially important. You might see errors thrown when you
attempt to access an object or value that has not yet been retrieved
from the server. You might also see errors if you create a query that
is not meaningful in the current context, such as trying to retrieve
list items before loading the associated list. Finally, you must deal
with errors that happen in the middle of batch operations on the
server. All of these situations mean that you must pay special
attention to error handling in your CSOM solutions.
If you attempt to access a scalar property that has not been retrieved, you will receive a Property OrFieldNotInitializedException error. If you make a request to the server that is deemed invalid, you will receive a ClientRequestException error. If your LINQ query is invalid, you will receive an InvalidQueryExpressionException error. General errors thrown on the server during execution of a request will result in a ServerException error. Example 3 shows code that generates the various runtime errors you might see when working with the managed client object model.
Example 3. Handling request errors
string appWebUrl = Page.Request["SPAppWebUrl"];
using (ClientContext ctx = new ClientContext(appWebUrl))
{
try
{
//Fails because the object was not initialized
//Requires Load() and ExecuteQuery()
Response.Write(ctx.Web.Title);
}
catch (PropertyOrFieldNotInitializedException x)
{
Response.Write("<p>Property not initialized. " + x.Message + "</p>");
}
try
{
//Fails because Skip() and Take() are meaningless
//in the context of a list collection
ctx.Load(ctx.Web, w => w.Lists.Skip(5).Take(10));
ctx.ExecuteQuery();
}
catch (InvalidQueryExpressionException x)
{
Response.Write("<p>Invalid LINQ query. " + x.Message + "</p>");
}
try
{
//Fails because InvalidObject is a meaningless object
InvalidObject o = new InvalidObject(ctx, null);
ctx.Load(o);
ctx.ExecuteQuery();
}
catch (ClientRequestException x)
{
Response.Write("<p>Bad request. " + x.Message + "</p>");
}
try
{
//Fails because the list does not exist
//The failure occurs on the server during processing
ctx.Load(ctx.Web,w=>w.Lists);
List myList = ctx.Web.Lists.GetByTitle("Non-Existent List");
myList.Description = "A new description";
myList.Update();
ctx.ExecuteQuery();
}
catch (ServerException x)
{
Response.Write("<p>Exception on server. " + x.Message + "</p>");
}
}
After looking over the errors that can occur during operations, the ServerException stands out as noteworthy. This is because the ServerException
is thrown when an operation fails on the server. Furthermore, the
failing operation could be in the middle of a large batch of
operations, which can lead to unpredictable behavior. The fundamental
challenge with the batch model embodied in the client object model is
that you need a way to respond to errors that happen on the server so
that the remainder of the batch operations can finish processing. The ServerException error is thrown on the client after the batch has failed, which gives you no opportunity to correct the error.
Fortunately, CSOM provides a mechanism for sending error-handling
instructions to the server along with the batch operations. You can use
the ExceptionHandlingScope object to define a try-catch-finally
block that embodies server-side operations. If errors occur during
processing on the server, it is handled on the server by the code
embodied in the ExceptionHandlingScope object. Example 4 shows how exception-handling scopes are implemented in the managed client object model.
Example 4. Handling errors in a scope
string appWebUrl = Page.Request["SPAppWebUrl"];
using (ClientContext ctx = new ClientContext(appWebUrl))
{
//Set up error handling
ExceptionHandlingScope xScope = new ExceptionHandlingScope(ctx);
using (xScope.StartScope())
{
using (xScope.StartTry())
{
//Try to update the description of a list named "My List"
List myList = ctx.Web.Lists.GetByTitle("My List");
myList.Description = "A new description";
myList.Update();
}
using (xScope.StartCatch())
{
//Fails if the list "My List" does not exist
//So, we'll create a new list
ListCreationInformation listCI = new ListCreationInformation();
listCI.Title = "My List";
listCI.TemplateType = (int)ListTemplateType.GenericList;
listCI.QuickLaunchOption = Microsoft.SharePoint.Client.
QuickLaunchOptions.On;
List list = ctx.Web.Lists.Add(listCI);
}
using (xScope.StartFinally())
{
//Try to update the list now if it failed originally
List myList = ctx.Web.Lists.GetByTitle("My List");
if(myList.Description.Length==0)
{
myList.Description = "A new description";
myList.Update();
}
}
}
//Execute the entire try-catch as a batch!
ctx.ExecuteQuery();
}
The most important aspect of the code shown in Example 4 is that the ExecuteQuery
method is called only once and it appears after the code in the
exception handling scope. This means that all of the operations defined
in the exception handling scope are sent to the server in a single
batch. Initially, the server tries to update the description of the
target list. If this operation fails, the exception handling scope
assumes it is because the list does not exist. Therefore, the
exception-handling scope creates a new list with the correct name.
Finally, the description is updated for the newly created list.
The exception-handling scope provides a powerful way for
you to deal with errors that occur during batch processing, but it does
require some additional planning. For example, the code in Example 4
assumes that any failure is the result of a nonexistent list. However,
there are other reasons why the operation could fail, such as the end
user not having the rights to update the list. Fortunately, the ExceptionHandlingScope provides properties that help you to understand exactly what went wrong on the server. The ServerErrorCode, ServerErrorValue, and ServerStackTrace properties can all be used to analyze the server error and make a decision about how to proceed.