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

.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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 00:27:28