How to track changes in specific folder in SharePoint Document Library with Microsoft Graph .NET SDK

Introduction

Imagine a case when you want to track changes in a specific folder in a SharePoint document library. The application should interact without a signed-in user and should have read-only access to specific folder.

Register Entra application

To access the Microsoft Graph API, you need to register an application in the Entra admin portal.

  1. Login to the Entra admin portal and register a new application.

  1. The application will interact with the Graph API without a signed-in user. Therefore, create a new client secret.

Copy the client secret and store it for later use.

  1. To be able to track changes in specific folder in SharePoint, you need to add and grant application permission Files.SelectedOperations.Selected:

Go to the Overview page and copy the Application (client) ID and Directory (tenant) ID.

Add permission to access specific folder

Files.SelectedOperations.Selected permission is relatively new kind of permission in the Microsoft Graph API. It manages application access at the file or library folder level, providing access to one or more files.

Now, the application has the permission to access selected files, but we need to still specify which folder exactly can the application access and in which role.

Let's say, we know the SharePoint site ID.

To list all document libraries in the SharePoint site, use the following request:

GET https://graph.microsoft.com/v1.0/sites/{siteId}/drives

It gives us a driveId.

Now, we need to find out driveItemId of the folder we want to track changes in. Best way is to find drive item by a relative path;

GET https://graph.microsoft.com/v1.0/drives/{driveId}/root:/path/to/folder:/

It gives us a driveItemId.

We have driveId and driveItemId, so we can add permission to the application to access the folder.

POST https://graph.microsoft.com/v1.0/drives/{driveId}/items/{driveItemId}/permissions
{
  "roles": ["read"],
  "grantedTo": {
    "application": {
      "id": "<entra_app_id>"
    }
  }
}

Role can be either read, write, owner or fullcontrol. The role defines what the application can do with the items.

As you can see adding permission is quite simple, only challenge is to find out driveId and driveItemId.

If you want to track changes in two or more folders which do not have parent-child relationship, you need to add permission for each folder/file separately. You can even combine the role for each item, like read role for one folder, write role for another one.

C# console application

Create a new C# console application (latest .NET) and install the following NuGet packages:

  • Microsoft.Graph - Access the Microsoft Graph API
  • Azure.Identity - Provides Entra token authentication support

Implementation

Let's say, we want to track changes in the folder for 1 hour. Create a cancellation token source and schedule cancellation after 1 hour.

var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromHours(1));

Instantiate the GraphServiceClient class and authenticate with the client secret through the ClientSecretCredential class.

var credential = new ClientSecretCredential("{tenantId}", "{clientId}", "{clientSecret}");

var graphClient = new GraphServiceClient(credential);

Use clientId, tenantId and clientSecret from the entra app registration.

Define driveId and driveItemId of the folder you want to track changes in.

var driveId = "{driveId}"
var driveItemId = "{driveItemId}"

If you set the value of driveItemId to root, the application will still track changes only in the folders where it has access. Set driveItemId to root if you track changes in more than one folder without parent-child relationship.

Make an initial request to the delta endpoint to get the current content of the folder(s).

// store items to be able to check if item was added or modified
var items = new HashSet<string>();
// init request
var response = await graphClient.Drives[driveId].Items[driveItemId].Delta.GetAsDeltaGetResponseAsync(cancellationToken: cts.Token);

var pageIterator = PageIterator<DriveItem, Microsoft.Graph.Drives.Item.Items.Item.Delta.DeltaGetResponse>.CreatePageIterator(graphClient, response, (item) =>
{
    if (item.Name != "root")
    {
        var itemPath = item.ParentReference.Path[(item.ParentReference.Path.IndexOf("root:") + 5)..];
        if(!items.Contains(item.Id))
        {
            items.Add(item.Id);
            Console.WriteLine($"Existing item: {item.Name}, path: {itemPath}");
        }
    }
    return true;
});
await pageIterator.IterateAsync(cts.Token);
var deltaLink = pageIterator.Deltalink;

Store delta link for the next iteration. The Deltalink property of the pageIterator contains the delta link that we will use to get the next set of changes.

Now, create a while loop that will run until the cancellation token is canceled. Inside the loop, we will retrieve changes using the delta link every 2 minutes.

while(true)
{
    await Task.Delay(TimeSpan.FromMinutes(2), cts.Token);
    response = await graphClient.Drives[driveId].Items[driveItemId].Delta.WithUrl(deltaLink).GetAsDeltaGetResponseAsync(cancellationToken: cts.Token);
    pageIterator = PageIterator<DriveItem, Microsoft.Graph.Drives.Item.Items.Item.Delta.DeltaGetResponse>.CreatePageIterator(graphClient, response, (item) =>
    {
        if (item.Name != "root")
        {
            if (item.Deleted != null)
            {
                if (items.Contains(item.Id))
                    items.Remove(item.Id);
                Console.WriteLine($"Item removed: {item.Id}");
                return true;
            }
            var itemPath = item.ParentReference.Path[(item.ParentReference.Path.IndexOf("root:") + 5)..];
            if (!items.Contains(item.Id))
            {
                items.Add(item.Id);
                Console.WriteLine($"Item added: {item.Name}, path: {itemPath}");
            }
            else
            {
                Console.WriteLine($"Item updated: {item.Name}, path: {itemPath}");
            }
        }
        return true;
    });
    await pageIterator.IterateAsync(cts.Token);
    deltaLink = pageIterator.Deltalink;
}

Run the application and make some changes in the folder you are tracking. You will see the changes in the console.

Selected permissions: https://learn.microsoft.com/graph/permissions-selected-overview

1
Buy Me a Coffee at ko-fi.com
An error has occurred. This application may no longer respond until reloaded. Reload x