Search code examples
microsoft-graph-apionedrivemicrosoft-graph-sdksmicrosoft-graph-files

Microsoft.Graph SDK connects to user's OneDrive but Items returns NULL


I'm writing a utility in C# using the Microsoft.Graph SDK that connects and reads a user's OneDrive. I have created an App Registration, granted the application Files.Read.All permissions, and given Admin consent per the documentation.

enter image description here

I am able to connect to the Graph and the metadata for the OneDrive:

List<string> scopes = new List<string>();
scopes.Add("https://graph.microsoft.com/.default");

var authenticationProvider = new MsalAuthenticationProvider(cca, scopes.ToArray());
GraphServiceClient graphClient = new GraphServiceClient(authenticationProvider);

var drive = await graphClient.Users[userId].Drive
    .Request()
    .GetAsync();

It appears to correctly connect to the OneDrive, as evidenced by the properties that do return correct data like Quota, Owner, etc.

The issue is that the Items object is null, so I can't read any of the drive items:

enter image description here

I tried using the returned drive Id to access the drive directly, but received the same result:

var driveById = await graphClient.Drives[drive.Id]
    .Request()
    .GetAsync();

The few examples I found don't indicate any additional Request options or missing permissions. So how do I access the OneDrive Items?


Solution

  • The solution for this issue was presented in the comments, so I'm writing it up here for completeness.

    ORIGINAL CODE:

    var rootDrive = await GraphClient.Users[UserId].Drive.Request().GetAsync();
    

    This returns the metadata of the user's OneDrive, but does not capture the Children. We will need this information later, however, so the final solution uses both this reference and the updated code.

    UPDATED CODE:

    To do that, you need to reference the drive's Root and its Children:

    var driveItems = await GraphClient.Users[UserId].Drive
                                                    .Root
                                                    .Children
                                                    .Request()
                                                    .GetAsync();
    

    Doing so returns an IDriveItemChildrenCollectionPage:

    enter image description here

    PROCESS THE CHILDREN:

    For small samples, a standard foreach will work fine, but for larger collections you will need to implement a PageIterator (which I have not done yet). To get the children of this driveItem, you will need the drive Id of the root element as well as the current driveItem.Id:

    var children = await GraphClient.Drives[rootDriveId].Items[item.Id].Children.Request().GetAsync()
    

    Putting it altogether, it looks something like this:

    public async Task ListOneDrive()
    {
        var rootDrive = await GraphClient.Users[UserId].Drive.Request().GetAsync();
    
        var driveItems = await GraphClient.Users[UserId].Drive
                                                        .Root
                                                        .Children
                                                        .Request()
                                                        .GetAsync();
    
        foreach (var item in driveItems)
        {
            await ListDriveItem(rootDrive.Id, item);
        }
    }
    
    public async Task ListDriveItem(string rootDriveId, DriveItem item, int indentLevel = 1)
    {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < indentLevel; i++)
        {
            sb.Append($"  ");
        }
    
        if (item.Folder != null)
        {
            Console.WriteLine($"{sb.ToString()}> {item.Name}/");
    
            var children = await GraphClient.Drives[rootDriveId].Items[item.Id].Children.Request().GetAsync();
            foreach (var child in children)
            {
                await (ListDriveItem(rootDriveId, child, indentLevel + 1));
            }
        }
        else if (item.File != null)
        {
            Console.WriteLine($"{sb.ToString()}  {item.Name}");
        }
    }
    

    This example is from a Console app that uses a recursive call to drill down through all the folders. Both methods really should have a PageIterator as mentioned above.

    Update as of Microsoft.Graph 5.12.0

        var driveItem = await graphClient.Me.Drive.GetAsync();
        var driveItems = await graphClient.Drives[driveItem.Id].Items["root"].Children.GetAsync();
    
        foreach (var item in driveItems.Value)
        {
            await ListDriveItem(driveItem.Id, item);
        }
    

    and then for ListDriveItem function:

       public async Task ListDriveItem(string rootDriveId, DriveItem item, int indentLevel = 1)
        {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < indentLevel; i++)
            {
                sb.Append($"  ");
            }
    
            if (item.Folder != null)
            {
                Console.WriteLine($"{sb.ToString()}> {item.Name}/");
    
                var children = await graphClient.Drives[rootDriveId].Items[item.Id].Children.GetAsync();
                foreach (var child in children.Value)
                {
                    await (ListDriveItem(rootDriveId, child, indentLevel + 1));
                }
            }
            else if (item.FileObject != null)
            {
                Console.WriteLine($"{sb.ToString()}  {item.Name}");
            }
        }