使用Microsoft Graph获取共享日历事件失败求助
我正在尝试实现微软官方的共享日历事件获取示例,使用ASP.NET Core 3.1、Azure AD和Microsoft Graph API。目前已成功获取个人日历事件,以及共享日历的名称、ID、所有者等信息,但始终无法获取共享日历中的事件,也没找到相关可用示例。
现有代码
获取共享日历事件的代码
[Authorize] [AuthorizeForScopes(ScopeKeySection = "MicrosoftGraph:Scopes")] public class HomeController : Controller { private readonly ILogger<HomeController> _logger; private readonly GraphServiceClient graphServiceClient; private readonly MicrosoftIdentityConsentAndConditionalAccessHandler _consentHandler; private string[] _graphScopes; public HomeController(ILogger<HomeController> logger, GraphServiceClient gsc, MicrosoftIdentityConsentAndConditionalAccessHandler consentHandler ) { _logger = logger; graphServiceClient = gsc; this._consentHandler = consentHandler; _graphScopes = Startup.ConfigurationInstance.GetSection("MicrosoftGraph")["Scopes"].Split(" "); } [AuthorizeForScopes(ScopeKeySection = "MicrosoftGraph:Scopes")] public IActionResult Index() { User currentUser = null; try { currentUser = graphServiceClient.Me.Request().GetAsync().Result; } // Catch CAE exception from Graph SDK catch (ServiceException svcex) when (svcex.Message.Contains("Continuous access evaluation resulted in claims challenge")) { try { Console.WriteLine($"{svcex}"); string claimChallenge = WwwAuthenticateParameters.GetClaimChallengeFromResponseHeaders(svcex.ResponseHeaders); _consentHandler.ChallengeUser(_graphScopes, claimChallenge); return new EmptyResult(); } catch (Exception ex2) { _consentHandler.HandleException(ex2); } } var viewOptions = new List<QueryOption> { new QueryOption("startDateTime", DateTime.Today.ToString("o")), new QueryOption("endDateTime", DateTime.Today.AddDays(20).ToString("o")) }; var sha = graphServiceClient.Me.Calendars .Request() .Select(e => e.Id) .GetAsync().Result.ToList(); var x = new List<Event>(); foreach(var row in sha) { var id = row.Id; var data = graphServiceClient.Me.Calendars[id].Request(viewOptions) .Select(e => e.Events).GetAsync().Result; ICalendarEventsCollectionPage d1 = data != null? data.Events: null; if (d1 != null) { x.AddRange(d1.ToList()); } } } }
这段代码返回的事件始终为null。
尝试直接访问其他用户日历的代码
var x = graphServiceClient.Users["<email>"] .CalendarView .Request(viewOptions) .Select(e => new { e.Subject, e.Organizer, e.Start, e.End }).GetAsync().Result;
执行后返回access denied(访问被拒绝)。
配置文件(appsettings.json)
"AzureAd": { "Instance": "https://login.microsoftonline.com/", "Domain": "<domain>", "TenantId": "<id>", "ClientId": "<id>", "CallbackPath": "/signin-oidc", "SignedOutCallbackPath": "/signout-callback-oidc", "ClientSecret": "<secret>" }, "AllowedHosts": "*", "MicrosoftGraph": { "Scopes": "User.Read MailboxSettings.ReadWrite Calendars.ReadWrite.Shared", "BaseUrl": "https://graph.microsoft.com/v1.0" }
解决方案
1. 修正共享日历事件的获取方式
你当前通过graphServiceClient.Me.Calendars[id].Request(viewOptions).Select(e => e.Events)获取事件的方式不正确——Calendars[id]返回的是日历对象,直接Select(e => e.Events)不会自动加载事件列表。正确的做法是直接调用CalendarView的请求方法:
foreach(var row in sha) { var id = row.Id; // 直接请求该日历下的CalendarView事件 var eventsPage = graphServiceClient.Me.Calendars[id].CalendarView .Request(viewOptions) .Select(e => new { e.Subject, e.Organizer, e.Start, e.End }) .GetAsync().Result; if (eventsPage != null) { x.AddRange(eventsPage.ToList()); } }
CalendarView必须指定startDateTime和endDateTime参数,你已经定义的viewOptions可以直接复用。
2. 解决访问其他用户日历的权限问题
直接访问Users["<email>"].CalendarView需要额外权限:
- 委派权限(用户登录场景)下,需添加
Calendars.Read或Calendars.ReadWrite权限(Calendars.ReadWrite.Shared仅针对已共享给当前用户的日历); - 确保管理员已同意该租户级权限,或用户登录时已授权该权限。
如果目标是已共享给当前用户的日历,更推荐通过Me.Calendars找到对应日历ID再获取事件,这种方式权限要求更低。
3. 更新权限范围
当前你的Scopes仅包含Calendars.ReadWrite.Shared,若要访问其他用户的完整日历,需补充Calendars.Read权限。建议更新配置:
"Scopes": "User.Read MailboxSettings.ReadWrite Calendars.ReadWrite.Shared Calendars.Read"
更新后需让用户重新登录,以获取包含新权限的令牌。
4. 替换同步调用为异步模式
代码中使用.Result阻塞异步调用可能导致死锁,建议改为async/await模式:
[AuthorizeForScopes(ScopeKeySection = "MicrosoftGraph:Scopes")] public async Task<IActionResult> Index() { User currentUser = null; try { currentUser = await graphServiceClient.Me.Request().GetAsync(); } // Catch CAE exception from Graph SDK catch (ServiceException svcex) when (svcex.Message.Contains("Continuous access evaluation resulted in claims challenge")) { try { Console.WriteLine($"{svcex}"); string claimChallenge = WwwAuthenticateParameters.GetClaimChallengeFromResponseHeaders(svcex.ResponseHeaders); _consentHandler.ChallengeUser(_graphScopes, claimChallenge); return new EmptyResult(); } catch (Exception ex2) { _consentHandler.HandleException(ex2); } } var viewOptions = new List<QueryOption> { new QueryOption("startDateTime", DateTime.Today.ToString("o")), new QueryOption("endDateTime", DateTime.Today.AddDays(20).ToString("o")) }; var sha = await graphServiceClient.Me.Calendars .Request() .Select(e => e.Id) .GetAsync(); var x = new List<Event>(); foreach(var row in sha) { var id = row.Id; var eventsPage = await graphServiceClient.Me.Calendars[id].CalendarView .Request(viewOptions) .Select(e => new { e.Subject, e.Organizer, e.Start, e.End }) .GetAsync(); if (eventsPage != null) { x.AddRange(eventsPage.ToList()); } } return View(x); }
内容的提问来源于stack exchange,提问作者Eugene Chepurnyy

