C#中通过Microsoft Graph API获取用户及经理信息并解决分页问题
问题与解决方案:通过Microsoft Graph API获取用户及经理信息并处理分页
问题说明
- 核心需求:调用Microsoft Graph API获取用户的
displayName、jobTitle、department等字段,同时获取对应经理的displayName - 两个关键问题:
- 初始代码中无法直接从User对象的Manager属性中提取经理的
displayName - 重构后设置
Top=999无效,API最多返回100条数据,已通过odata.nextlink实现分页,但不确定是否为最优方案
- 初始代码中无法直接从User对象的Manager属性中提取经理的
初始代码
var userorg = await graphClient.Users .Request() .Select("AccountEnabled,displayName,Department,JobTitle,Manager") .Top(999) .GetAsync(); foreach (var user in userorg) { if (user.AccountEnabled == true) { Console.WriteLine(user.DisplayName); Console.WriteLine(user.Department); Console.WriteLine(user.JobTitle); } }
重构后代码
var csv = new StringBuilder(); var scopes = new[] { "https://graph.microsoft.com/.default" }; var clientId = "xxxx"; var tenantId = "xxxxx"; var clientSecret = "xxxxxx"; var options = new ClientSecretCredentialOptions { AuthorityHost = AzureAuthorityHosts.AzurePublicCloud, }; var clientSecretCredential = new ClientSecretCredential( tenantId, clientId, clientSecret, options); var graphClient = new GraphServiceClient(clientSecretCredential, scopes); int i = 0; string sToken = null; try { while (i == 0 || sToken != null) { if (i == 0) { var users = await graphClient.Users.GetAsync((requestConfiguration) => { requestConfiguration.QueryParameters.Expand = new string[] { "manager($select=displayName,id)" }; requestConfiguration.QueryParameters.Select = new string[] { "accountEnabled", "displayName", "UserPrincipalName", "department", "jobTitle", "manager" }; }); foreach (var user in users.Value) { if (user.AccountEnabled == true && user.UserPrincipalName.Contains("crebti.onmicrosoft.com") != true && user.UserPrincipalName.Contains("#EXT") != true) { string f = user.DisplayName; string s = user.Department; string t = user.JobTitle; string q = null; if (user.Manager != null) { var manager = await graphClient.Users[user.Manager.Id].GetAsync((requestConfiguration) => { requestConfiguration.QueryParameters.Select = new string[] { "displayName" }; }); q = manager.DisplayName; } var nL = $"{f},{s},{t},{q}"; csv.AppendLine(nL); } } if (users.OdataNextLink != null) { sToken = users.OdataNextLink; string tokin = "$skiptoken="; sToken = sToken.Substring(sToken.IndexOf(tokin) + tokin.Length); } i++; } else { string delta = "https://graph.microsoft.com/v1.0/users?$expand=manager($select=displayName,id),&$select=accountEnabled,displayName,UserPrincipalName,department,jobTitle,manager,&$skiptoken=" + sToken; var result = new Microsoft.Graph.Users.Delta.DeltaRequestBuilder(delta, graphClient.RequestAdapter); var users = await result.GetAsync(); foreach (var user in users.Value) { if (user.AccountEnabled == true && user.UserPrincipalName.Contains("crebti.onmicrosoft.com") != true && user.UserPrincipalName.Contains("#EXT") != true) { string f = user.DisplayName; string s = user.Department; string t = user.JobTitle; string q = null; if (user.Manager != null) { var manager = await graphClient.Users[user.Manager.Id].GetAsync((requestConfiguration) => { requestConfiguration.QueryParameters.Select = new string[] { "displayName" }; }); q = manager.DisplayName; } var nL = $"{f},{s},{t},{q}"; csv.AppendLine(nL); } } if (users.OdataNextLink != null) { sToken = users.OdataNextLink; string tokin = "$skiptoken="; sToken = sToken.Substring(sToken.IndexOf(tokin) + tokin.Length); //pipipopopo } else sToken = null; } } File.WriteAllText("Auditoria.csv", csv.ToString(), Encoding.UTF8); } catch (ODataError odataError) { MessageBox.Show(odataError.Error.Message, odataError.Error.Code); }
优化建议
1. 避免重复请求经理信息
重构代码中已经通过Expand=manager($select=displayName,id)获取了经理的displayName,无需再单独调用graphClient.Users[user.Manager.Id].GetAsync(),直接转换类型即可获取:
if (user.Manager != null) { q = ((User)user.Manager).DisplayName; }
2. 使用官方推荐的分页处理方式
手动解析OdataNextLink和拼接请求并非最优方案,Microsoft Graph SDK提供了PageIterator类,可自动处理分页逻辑,代码更简洁易维护:
var csv = new StringBuilder(); var scopes = new[] { "https://graph.microsoft.com/.default" }; var clientId = "xxxx"; var tenantId = "xxxxx"; var clientSecret = "xxxxxx"; var options = new ClientSecretCredentialOptions { AuthorityHost = AzureAuthorityHosts.AzurePublicCloud, }; var clientSecretCredential = new ClientSecretCredential(tenantId, clientId, clientSecret, options); var graphClient = new GraphServiceClient(clientSecretCredential, scopes); try { // 构建初始请求 var request = graphClient.Users.GetAsync(config => { config.QueryParameters.Expand = new[] { "manager($select=displayName)" }; config.QueryParameters.Select = new[] { "accountEnabled", "displayName", "UserPrincipalName", "department", "jobTitle", "manager" }; }); // 创建PageIterator自动处理分页 var pageIterator = PageIterator<User, UsersResponse>.CreatePageIterator( graphClient, await request, user => { if (user.AccountEnabled == true && !user.UserPrincipalName.Contains("crebti.onmicrosoft.com") && !user.UserPrincipalName.Contains("#EXT")) { var managerName = user.Manager != null ? ((User)user.Manager).DisplayName : null; csv.AppendLine($"{user.DisplayName},{user.Department},{user.JobTitle},{managerName}"); } return true; }, nextRequest => nextRequest // 保持后续请求的查询参数一致 ); // 执行迭代获取所有数据 await pageIterator.IterateAsync(); File.WriteAllText("Auditoria.csv", csv.ToString(), Encoding.UTF8); } catch (ODataError odataError) { MessageBox.Show(odataError.Error.Message, odataError.Error.Code); }
3. 关于Top参数的限制
Microsoft Graph API的用户列表端点默认最大单页返回100条数据,即使设置Top超过100也会被截断为100,这是API的硬性限制,因此必须通过分页获取全部数据,使用PageIterator是官方推荐的最优实现方案。
内容的提问来源于stack exchange,提问作者Raphael Aracelli
相关产品推荐
相关产品推荐

