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.
- Login to the Entra admin portal and register a new application.
- 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.
- 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