2/11/2022 Admin
Blazor Org Chart Manager Using The Microsoft Graph
Using the Syncfusion Blazor Diagram component, we can display an Organizational Chart, using Microsoft Blazor, from data contained in the Microsoft Graph. In addition, we can update the manager of the users, by dragging and dropping the boxes representing them.
If we configure and run the sample code (available on the Downloads page if this site), we can see all the users, in the group, and their manager in the Microsoft Graph.
The Home page will display an Organizational Chart using the Syncfusion Blazor Diagram component, using the data in the Microsoft Graph.
We can also select a note on the Org Chart and drag it on top of another node to set the Manager in the Microsoft Graph.
Azure Set-Up
If you are an Administrator in your Microsoft Azure Tenant, you can also go to https://portal.azure.com/ and select the Azure Active Directory node.
Select Groups, then New Group.
Create a Security group called Organizational Chart.
Select the group and add members to the group.
Now you need to ensure each member, that has a Manger, has a Manger assigned
To do this, you can select a user, select their Profile, and select Edit to edit a user…
...and set their Manager in the Microsoft Graph.
You can also set their Job title.
Note: If you have a subscription to Microsoft 365, and are the Administrator, you can also go to the Microsoft 365 admin center (https://admin.microsoft.com/), select a user, and set their Manager.
Next, follow this link to find the directions to Register a Microsoft Graph application.
Ensure you add these permissions to the Azure Application Registration you create (and you click the Grant admin consent button after adding them):
- Directory.Read.All
- User.Read
- User.ReadWrite.All
The Code
Note: The sample code implements Syncfusion Blazor components. A license notification will show unless you obtain a license from: https://www.syncfusion.com/downloads/blazor
In the appsettings.json file of the sample application, fill in the settings from your Azure tenant and Azure Application Registration, in the AzureAd section.
The EmployeeGraphData.cs file contains the method that provides the data for both the Org Chart and the All Users page.
This is the code for the method:
public async Task<List<OrganizationalDetails>> GetEmployeesAsync()
{// Get Group Id
// This requires Directory.Read.All "Application" Permission
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };var options = new TokenCredentialOptions
{AuthorityHost = AzureAuthorityHosts.AzurePublicCloud};// https://docs.microsoft.com/dotnet/api/azure.identity.clientsecretcredential
var clientSecretCredential = new ClientSecretCredential(
_configuration["AzureAd:Tenant"],
_configuration["AzureAd:ClientId"],
_configuration["AzureAd:ClientSecret"],
options);var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
string OrganizationalChartGroup = "Organizational Chart";var GroupCollection = await graphClient.Groups.Request().Filter($"startsWith(displayName,'{OrganizationalChartGroup}')")
.GetAsync();var GroupId = GroupCollection.ToList().Where(g => g.DisplayName == OrganizationalChartGroup).FirstOrDefault().Id;// Get users and their Managers
// This requires User.Read.All "Application" Permission
IConfidentialClientApplication confidentialClientApplication =ConfidentialClientApplicationBuilder.Create(_configuration["AzureAd:ClientId"])
.WithTenantId(_configuration["AzureAd:Tenant"])
.WithClientSecret(_configuration["AzureAd:ClientSecret"])
.Build();AuthenticationResult result = null;
result = await confidentialClientApplication.AcquireTokenForClient(scopes).ExecuteAsync();var httpClient = new HttpClient();
var apiCaller = new ProtectedApiCallHelper(httpClient);
var callResult = await apiCaller.CallWebApiAndProcessResultASync("https://graph.microsoft.com/v1.0/groups/"
+ GroupId +"/members/microsoft.graph.user?$expand=manager($levels=max)",
result.AccessToken);List<GraphUser> colGraphUsers = new List<GraphUser>();
foreach (JProperty child in callResult.Properties().Where(p => !p.Name.StartsWith("@")))
{colGraphUsers.AddRange(child.Value.ToObject<List<GraphUser>>());}List<OrganizationalDetails> DataSource = new List<OrganizationalDetails>();
foreach (var objUser in colGraphUsers){DataSource.Add(new OrganizationalDetails()
{Id = objUser.id,Role = objUser.jobTitle,DisplayName = objUser.displayName,Manager = (objUser.manager != null) ? objUser.manager.id : "",
ManagerName = (objUser.manager != null) ? objUser.manager.displayName : ""
});}// Need to sort so the managers are at the top of the collection
return DataSource.OrderBy(x => x.Manager).ToList();
}
The Org Chart is contained on the Index.razor page.
The markup is shown below:
<div id="diagram-space" class="content-wrapper"><SfDiagramComponent @ref="Diagram" Height="690px" DragDrop="Drop"NodeCreating="NodeCreating" ConnectorCreating="ConnectorCreating"><DataSourceSettings Id="Id" ParentId="Manager" DataSource="colGraphUsers"></DataSourceSettings><Layout @bind-Type="type" @bind-HorizontalSpacing="@HorizontalSpacing"@bind-FixedNode="@FixedNode" @bind-Orientation="@oreintation"@bind-VerticalSpacing="@VerticalSpacing" @bind-HorizontalAlignment="@horizontalAlignment"@bind-VerticalAlignment="@verticalAlignment" GetLayoutInfo="GetLayoutInfo"><LayoutMargin @bind-Top="@top" @bind-Bottom="@bottom"@bind-Right="@right" @bind-Left="@left"></LayoutMargin></Layout><SnapSettings Constraints="SnapConstraints.None"></SnapSettings></SfDiagramComponent></div>
The data is retrieved in the OnInitializedAsync() method, from the GetEmployeesAsync() method:
protected override async Task OnInitializedAsync(){try
{colGraphUsers = await _EmployeeGraphData.GetEmployeesAsync();}catch (Exception ex)
{strError = ex.GetBaseException().Message;}}
The code to process the data and configure the Syncfusion Diagram Component is below:
// Defines default values for Node object
private void NodeCreating(IDiagramObject obj){Node node = obj as Node;
if (node.Data is System.Text.Json.JsonElement){node.Data = System.Text.Json.JsonSerializer.Deserialize<OrganizationalDetails>(node.Data.ToString());}OrganizationalDetails organizationData = node.Data as OrganizationalDetails;
node.Width = 177;node.Height = 54;node.Constraints = NodeConstraints.Default | NodeConstraints.AllowDrop;node.Annotations = new DiagramObjectCollection<ShapeAnnotation>()
{new ShapeAnnotation()
{Content = organizationData.DisplayName + "\r\n" + organizationData.Role,
Style = new TextStyle(){Color = "black"}}};}private void ConnectorCreating(IDiagramObject DiagramObject){Connector connector = (DiagramObject as Connector);
connector.Type = ConnectorSegmentType.Orthogonal;connector.TargetDecorator.Shape = DecoratorShape.None;}private TreeInfo GetLayoutInfo(IDiagramObject obj, TreeInfo options)
{if (rows == 0)
{if (rows == 0 && options.Rows != null)options.Rows = null;
Node node = obj as Node;
if (!options.HasSubTree)
{options.Orientation = subTreeOrientation;options.AlignmentType = subTreeAlignment;}}else
{if (!options.HasSubTree)
{options.AlignmentType = SubTreeAlignmentType.Balanced;options.Orientation = Orientation.Horizontal;options.Rows = rows;}}return options;
}
If a node is dropped on another node the following method is called:
private async Task Drop(DropEventArgs args)
{DiagramSelectionSettings selector = args.Element as DiagramSelectionSettings;
if (args.Target != null && args.Element != null && selector.Nodes.Count > 0){Node targetNode = args.Target as Node;
OrganizationalDetails targetNodeData =colGraphUsers.Find(data => data.Id == (targetNode.Data as OrganizationalDetails).Id);
OrganizationalDetails dragNodeData =colGraphUsers.Find(data => data.Id == (selector.Nodes[0].Data as OrganizationalDetails).Id);
// Update the Microsoft Graph
await _EmployeeGraphData.AssignManagerAsync(dragNodeData.Id, targetNodeData.Id);dragNodeData.Manager = targetNodeData.Id;Diagram.ClearSelection();await Diagram.RefreshDataSource();}}
That code calls the following method that actually updates the Microsoft Graph:
public async Task AssignManagerAsync(string EmployeeId, string ManagerId){// This requires User.ReadWrite.All "Application" Permission
// See: https://docs.microsoft.com/en-us/graph/api/user-post-manager?view=graph-rest-1.0&tabs=http
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };var options = new TokenCredentialOptions
{AuthorityHost = AzureAuthorityHosts.AzurePublicCloud};// https://docs.microsoft.com/dotnet/api/azure.identity.clientsecretcredential
var clientSecretCredential = new ClientSecretCredential(
_configuration["AzureAd:Tenant"],
_configuration["AzureAd:ClientId"],
_configuration["AzureAd:ClientSecret"],
options);var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
await graphClient.Users[EmployeeId].Manager.Reference.Request().PutAsync(ManagerId);}
Troubleshooting
If you get an error when running the sample code…
Access the Developer tools to see the error.
Clicking on the Console tab, and selecting the first error, will reveal the issue.
If you get the “The given key.. was not present in the dictionary” error, it is usually because you have more than one user who does not have a Manager set (so the diagram does not know where to start).
The solution is to alter the users placed in the group to only include users who have a manager properly assigned.
Download
The project is available on the Downloads page on this site.
You must have Visual Studio 2022 (or higher) installed to run the code.
Links
(Syncfusion) Create an Org Chart to Elegantly Visualize Hierarchical Data in Blazor WebAssembly
(Syncfusion Sample) Example of Remote Data in Blazor Diagram Component
(Syncfusion Sample) Organization Chart Example using Blazor Diagram Component
(Microsoft Graph) https://docs.microsoft.com/en-us/graph/api/user-post-manager?view=graph-rest-1.0&tabs=http
(Microsoft Graph) https://docs.microsoft.com/en-us/graph/api/user-list-manager?view=graph-rest-1.0&tabs=http
(Microsoft Graph) https://docs.microsoft.com/en-us/graph/api/user-list-directreports?view=graph-rest-1.0&tabs=http
(Microsoft Graph) https://docs.microsoft.com/en-us/graph/api/user-list-manager?view=graph-rest-1.0&tabs=csharp%2Chttp#example-2-get-manager-chain-up-to-the-root-level