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.

 

image

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.

 

image

The Home page will display an Organizational Chart using the Syncfusion Blazor Diagram component, using the data in the Microsoft Graph.

 

image

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

image

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.

 

image

Select Groups, then New Group.

 

image

Create a Security group called Organizational Chart.

 

image

Select the group and add members to the group.

 

image

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…

 

image

...and set their Manager in the Microsoft Graph.

You can also set their Job title.

 

image

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.

 

image

Next, follow this link to find the directions to Register a Microsoft Graph application.

 

image

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

image

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.

 

image

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();
        }

 

image

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

image

If you get an error when running the sample code…

 

image

Access the Developer tools to see the error.

 

image

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

An unhandled error has occurred. Reload 🗙