Cesivi Server - CSOM Programming Guide¶
Version: 2.0 Last Updated: 2026-03-28
Table of Contents¶
- Overview
- Setup
- Basic Operations
- List Operations
- List Item Operations
- File Operations
- User and Group Operations
- Known Limitations
- Best Practices
Overview¶
Cesivi Server supports the SharePoint Client-Side Object Model (CSOM) for .NET applications. This enables you to programmatically interact with Cesivi using the familiar SharePoint CSOM API.
Supported CSOM Version: SharePoint Server 2019 / SharePoint Server Subscription Edition
Compatibility: 100% of common CSOM operations are supported (as of 2026-03-28) - CSOM Test Pass Rate: 100% (580P/0F/5S across 49 test classes) - Lambda expressions: ✅ Fully working - Property selection: ✅ Fully working - Nested Include(): ✅ Fully working
Setup¶
Installation¶
Install the SharePoint CSOM NuGet packages:
Install-Package Microsoft.SharePointOnline.CSOM -Version 16.1.24211.12000
Or for on-premises SharePoint Server:
Install-Package Microsoft.SharePoint2019.CSOM -Version 16.0.10372.20015
Basic Connection¶
using Microsoft.SharePoint.Client;
// Create client context
var siteUrl = "http://localhost:5000/Default/RootSite";
using (var context = new ClientContext(siteUrl))
{
// Configure authentication
context.Credentials = new NetworkCredential("username", "password");
// Or use Basic authentication
// context.Credentials = new SharePointOnlineCredentials("username", securePassword);
// Execute operations
Web web = context.Web;
context.Load(web, w => w.Title, w => w.Description);
context.ExecuteQuery();
Console.WriteLine($"Site: {web.Title}");
Console.WriteLine($"Description: {web.Description}");
}
Basic Operations¶
Get Current Web¶
using (var context = new ClientContext(siteUrl))
{
context.Credentials = credentials;
Web web = context.Web;
context.Load(web);
context.ExecuteQuery();
Console.WriteLine($"Title: {web.Title}");
Console.WriteLine($"URL: {web.Url}");
Console.WriteLine($"Created: {web.Created}");
}
Load Specific Properties ✅ WORKING¶
// ✅ Load only specific properties (now fully working!)
context.Load(web,
w => w.Title,
w => w.Description,
w => w.Created);
context.ExecuteQuery();
// ✅ Works with multiple properties
context.Load(list,
l => l.Title,
l => l.Description,
l => l.Id,
l => l.ItemCount);
context.ExecuteQuery();
Status: ✅ Lambda expressions with property selection now work correctly (as of 2026-01-22, PLAN-171).
List Operations¶
Create a List¶
using (var context = new ClientContext(siteUrl))
{
context.Credentials = credentials;
Web web = context.Web;
ListCreationInformation creationInfo = new ListCreationInformation
{
Title = "Project Tasks",
Description = "Task tracking list",
TemplateType = (int)ListTemplateType.Tasks // 107
};
List list = web.Lists.Add(creationInfo);
context.Load(list);
context.ExecuteQuery();
Console.WriteLine($"List created: {list.Title} (ID: {list.Id})");
}
List Template Types:
- GenericList (100) - Custom list
- DocumentLibrary (101) - Document library
- Tasks (107) - Task list
- Calendar (106) - Calendar
- Announcements (104) - Announcements
Get a List¶
// Get by title
List list = web.Lists.GetByTitle("Project Tasks");
context.Load(list);
context.ExecuteQuery();
// Get by ID
List listById = web.Lists.GetById(listGuid);
context.Load(listById);
context.ExecuteQuery();
Get All Lists¶
ListCollection lists = web.Lists;
context.Load(lists);
context.ExecuteQuery();
foreach (List list in lists)
{
Console.WriteLine($"{list.Title} ({list.ItemCount} items)");
}
Update a List¶
List list = web.Lists.GetByTitle("Project Tasks");
list.Description = "Updated description";
list.EnableVersioning = true;
list.Update();
context.ExecuteQuery();
Delete a List¶
List list = web.Lists.GetByTitle("Project Tasks");
list.DeleteObject();
context.ExecuteQuery();
List Item Operations¶
Create an Item¶
List list = web.Lists.GetByTitle("Project Tasks");
ListItemCreationInformation itemCreateInfo = new ListItemCreationInformation();
ListItem newItem = list.AddItem(itemCreateInfo);
newItem["Title"] = "Complete documentation";
newItem["Status"] = "Active";
newItem["Priority"] = 10;
newItem["AssignedTo"] = new FieldUserValue { LookupId = userId };
newItem.Update();
context.ExecuteQuery();
Console.WriteLine($"Item created with ID: {newItem.Id}");
Get All Items¶
List list = web.Lists.GetByTitle("Project Tasks");
CamlQuery query = CamlQuery.CreateAllItemsQuery();
ListItemCollection items = list.GetItems(query);
context.Load(items);
context.ExecuteQuery();
foreach (ListItem item in items)
{
Console.WriteLine($"{item.Id}: {item["Title"]}");
}
Query Items with CAML¶
CamlQuery query = new CamlQuery();
query.ViewXml = @"
<View>
<Query>
<Where>
<Eq>
<FieldRef Name='Status' />
<Value Type='Choice'>Active</Value>
</Eq>
</Where>
<OrderBy>
<FieldRef Name='Priority' Ascending='FALSE' />
</OrderBy>
</Query>
<RowLimit>10</RowLimit>
</View>";
ListItemCollection items = list.GetItems(query);
context.Load(items);
context.ExecuteQuery();
foreach (ListItem item in items)
{
Console.WriteLine($"{item["Title"]} - Priority: {item["Priority"]}");
}
Get a Specific Item¶
List list = web.Lists.GetByTitle("Project Tasks");
ListItem item = list.GetItemById(1);
context.Load(item);
context.ExecuteQuery();
Console.WriteLine($"Title: {item["Title"]}");
Console.WriteLine($"Status: {item["Status"]}");
Update an Item¶
ListItem item = list.GetItemById(1);
item["Title"] = "Updated title";
item["Status"] = "Completed";
item.Update();
context.ExecuteQuery();
Delete an Item¶
ListItem item = list.GetItemById(1);
item.DeleteObject();
context.ExecuteQuery();
File Operations¶
Upload a File¶
List library = web.Lists.GetByTitle("Documents");
context.Load(library, l => l.RootFolder);
context.ExecuteQuery();
string fileName = "test-document.txt";
byte[] fileContent = System.Text.Encoding.UTF8.GetBytes("File content here");
FileCreationInformation fileInfo = new FileCreationInformation
{
Content = fileContent,
Url = fileName,
Overwrite = true
};
File uploadedFile = library.RootFolder.Files.Add(fileInfo);
context.Load(uploadedFile);
context.ExecuteQuery();
Console.WriteLine($"File uploaded: {uploadedFile.Name}");
Download a File¶
File file = web.GetFileByServerRelativeUrl("/Default/RootSite/Documents/test-document.txt");
context.Load(file);
context.ExecuteQuery();
ClientResult<Stream> stream = file.OpenBinaryStream();
context.ExecuteQuery();
using (FileStream fileStream = System.IO.File.Create("downloaded-file.txt"))
{
stream.Value.CopyTo(fileStream);
}
Get File Properties¶
File file = web.GetFileByServerRelativeUrl("/Default/RootSite/Documents/test-document.txt");
context.Load(file,
f => f.Name,
f => f.Length,
f => f.TimeCreated,
f => f.TimeLastModified);
context.ExecuteQuery();
Console.WriteLine($"Name: {file.Name}");
Console.WriteLine($"Size: {file.Length} bytes");
Console.WriteLine($"Created: {file.TimeCreated}");
User and Group Operations¶
Get Current User¶
User currentUser = web.CurrentUser;
context.Load(currentUser);
context.ExecuteQuery();
Console.WriteLine($"Current user: {currentUser.Title} ({currentUser.Email})");
Get a User¶
User user = web.EnsureUser("user@example.com");
context.Load(user);
context.ExecuteQuery();
Console.WriteLine($"User: {user.Title} (ID: {user.Id})");
Get All Users¶
Web web = context.Web;
UserCollection users = web.SiteUsers;
context.Load(users);
context.ExecuteQuery();
foreach (User user in users)
{
Console.WriteLine($"{user.Title} ({user.Email})");
}
Get a Group¶
Group group = web.SiteGroups.GetByName("Owners");
context.Load(group);
context.ExecuteQuery();
Console.WriteLine($"Group: {group.Title} (ID: {group.Id})");
Add User to Group¶
User user = web.EnsureUser("user@example.com");
Group group = web.SiteGroups.GetByName("Members");
group.Users.AddUser(user);
context.ExecuteQuery();
Known Limitations¶
1. Lambda Expression Support ✅ RESOLVED¶
Status: ✅ WORKING AS OF 2026-01-22 (PLAN-171)
Lambda expressions in Load() statements now work correctly:
✅ Now Fully Supported:
// ✅ Simple properties
context.Load(list, l => l.Title, l => l.Description, l => l.Id);
context.ExecuteQuery();
// ✅ Nested Include() expressions
context.Load(web.Lists, lists => lists.Include(
l => l.Title,
l => l.Description,
l => l.ItemCount
));
context.ExecuteQuery();
// ✅ Complex property selections
context.Load(web, w => w.Title, w => w.Description, w => w.Url, w => w.Created);
context.ExecuteQuery();
Test Coverage:
- 25/25 tests passing (100%) in LoadLambdaTests.cs
- Simple properties, multiple properties, nested Include() all working
2. Selective Property Loading ✅ RESOLVED¶
Status: ✅ WORKING AS OF 2026-01-22 (PLAN-171)
The Load() method with lambda property selectors now works correctly:
✅ Recommended Approach (all work now):
// Option 1: Load all properties
context.Load(list);
context.ExecuteQuery();
// Option 2: Load specific properties (now works!)
context.Load(list, l => l.Title, l => l.ItemCount);
context.ExecuteQuery();
// Option 3: Load with Include (now works!)
context.Load(web.Lists, lists => lists.Include(l => l.Title, l => l.Id));
context.ExecuteQuery();
3. AsEnumerable() in PnP Provisioning¶
Issue: Using AsEnumerable() on collections loaded via CSOM may cause CollectionNotInitializedException.
Workaround:
// Use foreach instead of LINQ
foreach (var item in collection)
{
// Process item
}
// Instead of
// collection.AsEnumerable().Where(x => ...).ToList();
See Also: KNOWN_LIMITATIONS.md for comprehensive list of limitations
Best Practices¶
1. Batch Operations¶
Execute all Load() calls together to minimize round-trips:
// Good - Single ExecuteQuery()
context.Load(web);
context.Load(web.Lists);
context.Load(currentUser);
context.ExecuteQuery();
// Bad - Multiple ExecuteQuery() calls
context.Load(web);
context.ExecuteQuery(); // ❌
context.Load(web.Lists);
context.ExecuteQuery(); // ❌
2. Error Handling¶
Always wrap CSOM operations in try-catch blocks:
try
{
List list = web.Lists.GetByTitle("NonExistentList");
context.Load(list);
context.ExecuteQuery();
}
catch (ServerException ex)
{
Console.WriteLine($"CSOM Error: {ex.Message}");
Console.WriteLine($"Server error code: {ex.ServerErrorCode}");
}
3. Dispose Context¶
Always dispose ClientContext to free resources:
using (var context = new ClientContext(siteUrl))
{
// Operations here
} // Automatically disposed
4. Use Proper Field Types¶
When setting field values, use appropriate field types:
// User field
item["AssignedTo"] = new FieldUserValue { LookupId = userId };
// Lookup field
item["Category"] = new FieldLookupValue { LookupId = categoryId };
// Multi-choice field
item["Tags"] = new string[] { "Tag1", "Tag2" };
// Date field
item["DueDate"] = DateTime.Now.AddDays(7);
Example: Complete Workflow¶
Here's a complete example demonstrating common operations:
using System;
using Microsoft.SharePoint.Client;
class Program
{
static void Main()
{
var siteUrl = "http://localhost:5000/Default/RootSite";
var credentials = new NetworkCredential("admin", "password");
using (var context = new ClientContext(siteUrl))
{
context.Credentials = credentials;
// 1. Create a list
Web web = context.Web;
ListCreationInformation creationInfo = new ListCreationInformation
{
Title = "Project Tasks",
TemplateType = (int)ListTemplateType.Tasks
};
List list = web.Lists.Add(creationInfo);
context.Load(list);
context.ExecuteQuery();
Console.WriteLine($"List created: {list.Title}");
// 2. Add items
for (int i = 1; i <= 5; i++)
{
ListItem item = list.AddItem(new ListItemCreationInformation());
item["Title"] = $"Task {i}";
item["Status"] = i % 2 == 0 ? "Completed" : "Active";
item["Priority"] = i * 2;
item.Update();
}
context.ExecuteQuery();
Console.WriteLine("5 items added");
// 3. Query items
CamlQuery query = new CamlQuery();
query.ViewXml = @"
<View>
<Query>
<Where>
<Eq>
<FieldRef Name='Status' />
<Value Type='Choice'>Active</Value>
</Eq>
</Where>
</Query>
</View>";
ListItemCollection items = list.GetItems(query);
context.Load(items);
context.ExecuteQuery();
Console.WriteLine($"\nActive tasks:");
foreach (ListItem item in items)
{
Console.WriteLine($" {item["Title"]} - Priority: {item["Priority"]}");
}
// 4. Update an item
ListItem firstItem = list.GetItemById(1);
firstItem["Status"] = "Completed";
firstItem.Update();
context.ExecuteQuery();
Console.WriteLine("\nUpdated item 1");
// 5. Delete an item
ListItem lastItem = list.GetItemById(5);
lastItem.DeleteObject();
context.ExecuteQuery();
Console.WriteLine("Deleted item 5");
// 6. Verify final count
items = list.GetItems(CamlQuery.CreateAllItemsQuery());
context.Load(items);
context.ExecuteQuery();
Console.WriteLine($"\nFinal item count: {items.Count}");
}
}
}
Additional Resources¶
- API_REFERENCE.md - REST API reference documentation
- ODATA_QUERY_GUIDE.md - OData query syntax and examples
- KNOWN_LIMITATIONS.md - Comprehensive list of known limitations
- PNP_COMPATIBILITY.md - PnP PowerShell compatibility guide
Last Updated: 2026-03-28 Version: 2.0 Compatibility: SharePoint Server 2019 / SharePoint Server Subscription Edition CSOM