using SharepointToolbox.Infrastructure.Auth; namespace SharepointToolbox.Services; /// /// Searches tenant users via Microsoft Graph API. /// Used by the people-picker autocomplete in the User Access Audit tab. /// public class GraphUserSearchService : IGraphUserSearchService { private readonly GraphClientFactory _graphClientFactory; public GraphUserSearchService(GraphClientFactory graphClientFactory) { _graphClientFactory = graphClientFactory; } public async Task> SearchUsersAsync( string clientId, string query, int maxResults = 10, CancellationToken ct = default) { if (string.IsNullOrWhiteSpace(query) || query.Length < 2) return Array.Empty(); var graphClient = await _graphClientFactory.CreateClientAsync(clientId, ct); // Use $filter with startsWith on displayName and mail. // Graph API requires ConsistencyLevel=eventual for advanced queries. var escapedQuery = query.Replace("'", "''"); var response = await graphClient.Users.GetAsync(config => { config.QueryParameters.Filter = $"startsWith(displayName,'{escapedQuery}') or startsWith(mail,'{escapedQuery}') or startsWith(userPrincipalName,'{escapedQuery}')"; config.QueryParameters.Select = new[] { "displayName", "userPrincipalName", "mail" }; config.QueryParameters.Top = maxResults; config.QueryParameters.Orderby = new[] { "displayName" }; config.Headers.Add("ConsistencyLevel", "eventual"); config.QueryParameters.Count = true; }, ct); if (response?.Value is null) return Array.Empty(); return response.Value .Select(u => new GraphUserResult( DisplayName: u.DisplayName ?? u.UserPrincipalName ?? "Unknown", UserPrincipalName: u.UserPrincipalName ?? string.Empty, Mail: u.Mail)) .ToList(); } }