如何在Microsoft Graph API v5中结合PageIterator使用yield return
使用Graph API v5实现带yield return的IAsyncEnumerable获取组成员
问题背景
旧版Graph API SDK中,可通过NextPageRequest手动分页,结合yield return实现IAsyncEnumerable<Microsoft.Graph.User>流式返回组成员:
public async IAsyncEnumerable<Microsoft.Graph.User> LoadGroupMembers(string GroupId) { if (graphClient == null) throw new ArgumentNullException(nameof(graphClient)); var users = await graphClient.Groups[GroupId].Members .Request() .GetAsync(); do { foreach (var member in users) { if (member is Microsoft.Graph.User user) { yield return user; } } } while (users.NextPageRequest != null && (users = await users.NextPageRequest.GetAsync()).Count > 0); }
升级到v5版本后,SDK移除了NextPageRequest,改用PageIterator处理分页,但PageIterator的回调无法直接使用yield return,需找到替代方案实现流式返回用户数据。
解决方案
方案1:借助Channel实现异步数据传递
利用System.Threading.Channels作为异步缓冲区,在PageIterator回调中发送数据,同时从通道读取并通过yield return返回:
using System.Threading.Channels; public async IAsyncEnumerable<Microsoft.Graph.User> LoadGroupMembers(string groupId) { if (graphClient == null) throw new ArgumentNullException(nameof(graphClient)); var channel = Channel.CreateUnbounded<Microsoft.Graph.User>(); var users = await graphClient.Groups[groupId].Members.GetAsync(); var pageIterator = PageIterator<User, UserCollectionResponse>.CreatePageIterator( graphClient, users, user => { channel.Writer.TryWrite(user); return true; }, request => request ); // 后台执行分页迭代,完成后关闭通道写入端 _ = Task.Run(async () => { await pageIterator.IterateAsync(); channel.Writer.Complete(); }); // 从通道读取数据并流式返回 await foreach (var user in channel.Reader.ReadAllAsync()) { yield return user; } }
代码说明
- Channel作为异步数据缓冲区,解决回调无法直接
yield return的问题 - 后台任务执行分页迭代,避免阻塞读取流程,迭代完成后关闭通道写入端
- 通过
await foreach读取通道数据,保持IAsyncEnumerable的流式特性
方案2:手动解析分页令牌
模拟旧版分页逻辑,直接解析OdataNextLink中的skipToken发起下一页请求:
public async IAsyncEnumerable<Microsoft.Graph.User> LoadGroupMembers(string groupId) { if (graphClient == null) throw new ArgumentNullException(nameof(graphClient)); string? skipToken = null; do { var response = await graphClient.Groups[groupId].Members .GetAsync(requestConfiguration => { requestConfiguration.QueryParameters.SkipToken = skipToken; }); foreach (var member in response.Value) { if (member is Microsoft.Graph.User user) yield return user; } skipToken = response.OdataNextLink != null ? ExtractSkipToken(response.OdataNextLink) : null; } while (skipToken != null); } // 从OdataNextLink中提取skipToken的辅助方法 private string? ExtractSkipToken(string nextLink) { var uri = new Uri(nextLink); var queryParams = System.Web.HttpUtility.ParseQueryString(uri.Query); return queryParams["$skiptoken"]; }
代码说明
- 直接处理分页令牌,逻辑与旧版实现一致,无需依赖
PageIterator - 同样支持
yield return流式返回用户数据
内容的提问来源于stack exchange,提问作者Richard Hubley
相关产品推荐
相关产品推荐

