This article is contributed. See the original author and article here.

Hi, I am Samson Amaugo, a Microsoft MVP and a Microsoft Learn Student Ambassador. I love writing and talking about all things DotNet. I am currently a student at the Federal University of Technology, Owerri. We could connect on Linkedin at My Linkedin Profile.

Have you ever thought about going through all your GitHub Repositories, taking note of the languages used, aggregating them, and visualizing it on Excel?


 


Well, that is what this post is all about except you don’t have to do it manually in a mundane way.


With the Aid of the Octokit GraphQL Library and Microsoft Graph .NET SDK, you could code up a cool that automates this process.


 


To build out this project in a sandboxed environment with the appropriate permissions I had to sign up on Microsoft 365 Dev Center to Create an Account that I could use to interact with Microsoft 365 products.



The outcome of the project could be seen below


samson2ky_0-1677433194567.png


 


Steps to Build


Generating an Access Token From GitHub



  • I went to https://github.com/settings/apps and selected the Tokens (classic) option. This is because that is what the Octokit GraphQL library currently supports.

  • I clicked on the Generate new token dropdown.

  • I clicked on the Generate new token (classic) option.


samson2ky_1-1677433228272.png


 



  • I filled in the required Note field and selected the repo scope which would enable the token to have access to both my private and public repositories. After that, I scrolled down and selected the Generate token button.


samson2ky_2-1677433259708.png


 



  • Next, I copied the generated token to use during my code development.


samson2ky_3-1677433278279.png


 


Creating a Microsoft 365 Developer Account



samson2ky_4-1677433313027.png


 


Registering an Application on Azure



  • To interact with Microsoft 365 applications via the graph API, I had to register an app on Azure Active Directory.

  • I signed in to https://aad.portal.azure.com/ (Azure Active Directory Admin Center) using the developer email gotten from the Microsoft 365 developer subscription (i.e. samson2ky@q8w6.onmicrosoft.com).

  • I clicked on the Azure Active Directory menu on the left menu pane.

  • I clicked on the App Registration option.

  • I clicked on the New registration option.


samson2ky_5-1677433336096.png


 



  • I filled in the application name and clicked on the Register button.
    samson2ky_7-1677433416335.png

     


     



  • I copied the clientId and the tenantId

  • I clicked on the API permissions menu on the left pane
    samson2ky_8-1677433438539.png

     




 


 



  • To grant the registered application access to manipulate files, I had to grant it read and write access to my files.

  • I clicked on the Add a permission option.
    samson2ky_9-1677433475329.png

     



  • I clicked on the Microsoft Graph API.


samson2ky_10-1677433502832.png


 



  • Since I wanted the application to run in the background without signing in as a user, I selected the Application permissions option, typed files in the available field for easier selection, checked the Files.ReadWrite.All permission and clicked on the Add permissions button.


samson2ky_11-1677433516544.png


 



  • At this point, I had to grant the application ADMIN consent before it would be permitted to read and write to my files.


 


samson2ky_12-1677433538363.png


 


samson2ky_13-1677433555615.png


 



  • Next, I had to generate the client’s secret by clicking on the Certificates & secrets menu on the left panel and clicking on the New client secret button.

    samson2ky_14-1677433583262.png

     



  • I filled up the client’s secret description and clicked on the Add button.
    samson2ky_15-1677433596498.png

     



  • Finally, I copied the Value of the generated secret key.
    samson2ky_16-1677433614069.png

     




Writing the Code



  • I created a folder, opened a terminal in the directory and executed the command below to bootstrap my console application.
    dotnet new console

  • I installed some packages which you can easily install by adding the item group below in your .csproj file within the Project tag.


 


    
    
    
    
    
    
 

 



  • Next, execute the command below to install them
    dotnet restore

  • To have my necessary configuration in a single place, I created an appsettings.json file at the root of my project and added the JSON data below:


 

{
    "AzureClientID": "eff50f7f-6900-49fb-a245-168fa53d2730",
    "AzureClientSecret": "vUx8Q~plb15_Q~2ZscyfxKnR6VrWm634lIYVRb.V",
    "AzureTenantID": "33f6d3c4-7d26-473b-a7f0-13b53b72b52b",
    "GitHubClientSecret": "ghp_rtPprvqRPlykkYofA4V36EQPNV4SK210LNt7",
    "NameOfNewFile": "chartFile.xlsx"
}

 



You would need to replace the credential above with yours.



  • In the above JSON, you can see that I populated the values of the key with the saved secrets and IDs I obtained during the GitHub token registration and from the Azure Directory Application I created.

  • To be able to bind the values in the JSON above to a C# class, I created a Config.cs file in the root of my project and added the code below:


 

using Microsoft.Extensions.Configuration;
namespace MicrosoftGraphDotNet
{
    internal class Config
    {
        // Define properties to hold configuration values
        public string? AzureClientId { get; set; }
        public string? AzureClientSecret { get; set; }
        public string? AzureTenantId { get; set; }
        public string? GitHubClientSecret { get; set; }
        public string? NameOfNewFile { get; set; }
        // Constructor to read configuration values from appsettings.json file
        public Config()
        {
            // Create a new configuration builder and add appsettings.json as a configuration source
            IConfiguration config = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json")
                .Build();

            // Bind configuration values to the properties of this class
            config.Bind(this);
        }
    }
}

 


 


In the Program.cs file I imported the necessary namespaces that I would be needing by adding the code below:


 


 

// Import necessary packages
using System.Text.Json;
using Octokit.GraphQL;
using Octokit.GraphQL.Core;
using Octokit.GraphQL.Model;
using Azure.Identity;
using Microsoft.Graph;
using MicrosoftGraphDotNet;

 



  • Next, I instantiated my, Config class:


 

// retrieve the config
var config = new Config();

 


 



  • I made use of the Octokit GraphQL API to query all my GitHub repositories for repository names and the languages used in individual repositories. Then I created a variable to hold the list of distinct languages available in all my repositories. After that, I deserialized the response into an array of a custom class I created (Repository class).



 

// Define user agent and connection string for GitHub GraphQL API
var userAgent = new ProductHeaderValue("YOUR_PRODUCT_NAME", "1.0.0");
var connection = new Connection(userAgent, config.GitHubClientSecret!);

// Define GraphQL query to fetch repository names and their associated programming languages
var query = new Query()
.Viewer.Repositories(
isFork: false,
affiliations: new Arg<IEnumerable>(
new RepositoryAffiliation?[] { RepositoryAffiliation.Owner })
).AllPages().Select(repo => new
{
    repo.Name,
    Languages = repo.Languages(null, null, null, null, null).AllPages().Select(language => language.Name).ToList()
}).Compile();

// Execute the GraphQL query and deserialize the result into a list of repositories
var result = await connection.Run(query);
var languages = result.SelectMany(repo => repo.Languages).Distinct().ToList();
var repoNameAndLanguages = JsonSerializer.Deserialize(JsonSerializer.Serialize(result));

 


 



  • Since I am using top-level statements in my code I decided to ensure the custom class I created would be the last thing in the Program.cs file.


 

// Define a class to hold repository data
class Repository
{
    public string? Name { get; set; }
    public List? Languages { get; set; }
}

 


 



  • Now that I’ve written the code to retrieve my repository data, the next step is to write the code to create an Excel File, Create a table, create rows and columns, populate the rows and columns with data and use that to plot a chart that visualizes the statistics of the top GitHub Programming languages used throughout my repositories.

  • I initialized the Microsoft Graph .NET SDK using the :


 

// Define credentials and access scopes for Microsoft Graph API
var tokenCred = new ClientSecretCredential(
config.AzureTenantId!,
config.AzureClientId!,
config.AzureClientSecret!);
var graphClient = new GraphServiceClient(tokenCred);

 


 



  • Next, I created an Excel file :


 

// Define the file name and create a new Excel file in OneDrive
var driveItem = new DriveItem
{
    Name = config.NameOfNewFile!,
    File = new Microsoft.Graph.File
    {
    }
};
var newFile = await graphClient.Drive.Root.Children
.Request()
.AddAsync(driveItem);

 


 



  • I created a table that spans the length of the data I have horizontally and vertically:


 

// Define the address of the Excel table and create a new table in the file
var address = "Sheet1!A1:" + (char)('A' + languages.Count) + repoNameAndLanguages?.Count();
var hasHeaders = true;
var table = await graphClient.Drive.Items[newFile.Id].Workbook.Tables
.Add(hasHeaders, address)
.Request()
.PostAsync();

 



  • I created a 2-Dimensional List that would represent my data in the format below


samson2ky_17-1677433689596.png


 


The code that represents the data above can be seen below:


 

// Define the first row of the Excel table with the column headers
var firstRow = new List { "Repository Name" }.Concat(languages).ToList();

// Convert the repository data into a two-dimensional list
List<List> totalRows = new List<List> { firstRow };
foreach (var value in repoNameAndLanguages!)
{
    var row = new List { value.Name! };
    foreach (var language in languages)
    {
        row.Add(value.Languages!.Contains(language) ? "1" : "0");
    }
    totalRows.Add(row);
}

// Add a new row to the table with the total number of repositories for each language
var languageTotalRow = new List();
// Add "Total" as the first item in the list
languageTotalRow.Add("Total");
// Loop through each programming language in the header row
for (var languageIndex = 1; languageIndex < totalRows[0].Count; languageIndex++)
{
    // Set the total count for this language to 0
    var languageTotal = 0;
    // Loop through each repository in the table
    for (var repoIndex = 1; repoIndex < totalRows.Count; repoIndex++)
    {
        // If the repository uses this language, increment the count
        if (totalRows[repoIndex][languageIndex] == "1")
        {
            languageTotal++;
        }
    }

    // Add the total count for this language to the languageTotalRow list
    languageTotalRow.Add(languageTotal.ToString());
}

// Add the languageTotalRow list to the bottom of the table
totalRows.Add(languageTotalRow);

 


 



  • I added the rows of data to the table using the code below:


 

// Create a new WorkbookTableRow object with the totalRows list serialized as a JSON document
var workbookTableRow = new WorkbookTableRow
{
    Values = JsonSerializer.SerializeToDocument(totalRows),
    Index = 0,
};

// Add the new row to the workbook table
await graphClient.Drive.Items[newFile.Id].Workbook.
Tables[table.Id].Rows
.Request()
.AddAsync(workbookTableRow);

 


 



  • Finally, I created a ColumnClustered chart using my data and logged the URL of the spreadsheet.


 

// Add a new chart to the worksheet with the language totals as data
await graphClient.Drive.Items[newFile.Id].Workbook.Worksheets["Sheet1"].Charts
.Add("ColumnClustered", "Auto", JsonSerializer.SerializeToDocument($"Sheet1!B2:{(char)('A' + languages.Count)}2, Sheet1!B{repoNameAndLanguages.Count() + 3}:{(char)('A' + languages.Count)}{repoNameAndLanguages.Count() + 3}"))
.Request()
.PostAsync();

// Print the URL of the new file to the console
Console.WriteLine(newFile.WebUrl);

 


 



  • After executing the command: dotnet run, I got the URL or link to the excel file as an output.


samson2ky_19-1677434589898.png


 



  • On clicking the link I was able to view the awesome visualization of the languages used across my GitHub repositories.


samson2ky_18-1677433718596.png


 


And that’s the end of this article. I hope you enjoyed it and got to see how I used Microsoft Graph .NET SDK to automate this process.


 


To learn more about Microsoft Graph API and SDKs:
Microsoft Graph https://developer.microsoft.com/graph 
Develop apps with the Microsoft Graph Toolkit – Training


Hack Together: Microsoft Graph and .NET 

Is a hackathon for beginners to get started building scenario-based apps using .NET and Microsoft Graph. In this hackathon, you will kick-start learning how to build apps with Microsoft Graph and develop apps based on the given Top Microsoft Graph Scenarios, for a chance to win exciting prizes while meeting Microsoft Graph Product Group Leaders, Cloud Advocates, MVPs and Student Ambassadors. The hackathon starts on March 1st and ends on March 15th. It is recommended for participants to follow the Hack Together Roadmap for a successful hackathon.



Demo/Sample Code
You can access the code for this project at https://github.com/sammychinedu2ky/MicrosoftGraphDotNet


 

Brought to you by Dr. Ware, Microsoft Office 365 Silver Partner, Charleston SC.