You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#中通过Microsoft Graph API获取用户及经理信息并解决分页问题

问题与解决方案:通过Microsoft Graph API获取用户及经理信息并处理分页

问题说明

  • 核心需求:调用Microsoft Graph API获取用户的displayName、jobTitle、department等字段,同时获取对应经理的displayName
  • 两个关键问题:
    1. 初始代码中无法直接从User对象的Manager属性中提取经理的displayName
    2. 重构后设置Top=999无效,API最多返回100条数据,已通过odata.nextlink实现分页,但不确定是否为最优方案

初始代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 01:15:56