.NET调用Microsoft Graph API获取用户文件请求超时求助
.NET网站调用Microsoft Graph请求超时问题排查
问题场景
- 开发.NET网站应用,按教程引入所需依赖DLL并完成对应代码编写
- 注册带免费订阅的Microsoft个人账号(格式为xxxx@outlook.com),参照教程在Azure平台完成应用注册配置
- 代码运行后功能异常,执行
request.GetAsync().Result语句时耗时过长,最终触发请求超时错误
相关代码
default.aspx 后台逻辑
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using Microsoft.Identity.Client; using Microsoft.Graph; using Microsoft.Extensions.Configuration; using Helpers; using System.Security; public partial class _Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { var config = LoadAppSettings(); if (config == null) { iserror.Text = "config error"; return; } try { var userName = ReadUsername(); var userPassword = ReadPassword(); var client = GetAuthenticatedGraphClient(config, userName, userPassword); var request = client.Me.Drive.Root.Children.Request(); var results = request.GetAsync().Result; foreach (var file in results) { } } catch { iserror.Text = "config file exist. azure error"; } } private static SecureString ReadPassword() { var securePassword = "xxxxxxx"; SecureString password = new SecureString(); foreach (char c in securePassword) password.AppendChar(c); return password; } private static string ReadUsername() { string username; username = "xxx@outlook.com"; return username; } private static GraphServiceClient GetAuthenticatedGraphClient(IConfigurationRoot config, string userName, SecureString userPassword) { var authenticationProvider = CreateAuthorizationProvider(config, userName, userPassword); var graphClient = new GraphServiceClient(authenticationProvider); return graphClient; } private static IAuthenticationProvider CreateAuthorizationProvider(IConfigurationRoot config, string userName, SecureString userPassword) { var clientId = config["applicationId"]; var authority = "https://login.microsoftonline.com/" + config["tenantId"] + "/v2.0"; List<string> scopes = new List<string>(); scopes.Add("User.Read"); scopes.Add("Files.Read"); var cca = PublicClientApplicationBuilder.Create(clientId) .WithAuthority(authority) .Build(); return MsalAuthenticationProvider.GetInstance(cca, scopes.ToArray(), userName, userPassword); } private static IConfigurationRoot LoadAppSettings() { try { string asas = HttpContext.Current.Server.MapPath(""); var config = new ConfigurationBuilder() .SetBasePath(asas) .AddJsonFile("appsettings.json", false, true) .Build(); if (string.IsNullOrEmpty(config["applicationId"]) || string.IsNullOrEmpty(config["tenantId"])) { return null; } return config; } catch (System.IO.FileNotFoundException) { return null; } } }
MsalAuthenticationProvider.cs 认证实现
using System.Net.Http; using System.Net.Http.Headers; using System.Security; using System.Threading.Tasks; using Microsoft.Identity.Client; using Microsoft.Graph; namespace Helpers { public class MsalAuthenticationProvider : IAuthenticationProvider { private static MsalAuthenticationProvider _singleton; private IPublicClientApplication _clientApplication; private string[] _scopes; private string _username; private SecureString _password; private string _userId; private MsalAuthenticationProvider(IPublicClientApplication clientApplication, string[] scopes, string username, SecureString password) { _clientApplication = clientApplication; _scopes = scopes; _username = username; _password = password; _userId = null; } public static MsalAuthenticationProvider GetInstance(IPublicClientApplication clientApplication, string[] scopes, string username, SecureString password) { if (_singleton == null) { _singleton = new MsalAuthenticationProvider(clientApplication, scopes, username, password); } return _singleton; } public async Task AuthenticateRequestAsync(HttpRequestMessage request) { var accessToken = await GetTokenAsync(); request.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken); } public async Task<string> GetTokenAsync() { if (!string.IsNullOrEmpty(_userId)) { try { var account = await _clientApplication.GetAccountAsync(_userId); if (account != null) { var silentResult = await _clientApplication.AcquireTokenSilent(_scopes, account).ExecuteAsync(); return silentResult.AccessToken; } } catch (MsalUiRequiredException) { } } var result = await _clientApplication.AcquireTokenByUsernamePassword(_scopes, _username, _password).ExecuteAsync(); _userId = result.Account.HomeAccountId.Identifier; return result.AccessToken; } } }
appsettings.json 配置
{ "tenantId": "xxxx", "applicationId": "xxxx" }
排查方向
- 优先排查异步死锁问题:ASP.NET Web Forms存在专属同步上下文,在同步的Page_Load方法中直接调用
.Result阻塞异步方法,会触发经典的异步死锁,导致请求一直挂起直到超时。修复方式:给页面指令添加Async="true"属性,将Page_Load改为protected async void Page_Load(object sender, EventArgs e),全程用await调用异步方法,不要用.Result或.Wait()做阻塞;也可以在所有异步调用后追加.ConfigureAwait(false),避免捕获同步上下文引发死锁。 - 排查认证流适配问题:代码使用的
AcquireTokenByUsernamePassword即ROPC资源所有者密码流,完全不支持个人Outlook账号(MSA账号),仅支持Azure AD托管的组织域账号。个人账号调用该接口时无法完成认证流程,会长时间挂起最终超时。如果需要适配个人Outlook账号,必须替换为授权码流、设备码流等支持交互的认证流程,不能使用无交互的ROPC流。 - 排查租户配置错误:当前代码中authority拼接了配置文件里的tenantId,如果填入的是Azure AD目录的专属租户ID,个人账号会因为无权限访问该租户导致认证卡住。需要兼容个人账号时,authority中的租户标识必须改为
common,即https://login.microsoftonline.com/common/v2.0,不能填固定租户ID。 - 排查异常吞没问题:现有代码用无参数catch块捕获所有异常,仅返回统一错误文本,完全无法定位具体故障点。先修改catch块,输出异常的完整Message、InnerException和堆栈信息,先确认故障发生在令牌获取阶段还是Graph接口调用阶段,避免无效排查。
- 排查应用注册配置问题:使用公共客户端认证流时,必须在Azure AD应用注册的「身份验证」面板中,将应用标记为「公共客户端」,否则令牌请求会被服务端拒绝,导致请求挂起超时。
- 排查网络连通性问题:确认部署/运行环境可以正常访问Microsoft身份认证端点和Graph服务端点,检查本地代理、服务器防火墙、出站安全规则是否拦截了对应域名的请求,网络拦截同样会导致请求长时间无响应超时。
内容的提问来源于stack exchange,提问作者Soundar
相关产品推荐
相关产品推荐

