.NET 6 Startup中创建Microsoft Graph订阅的实现问题
.NET 启动阶段自动创建Microsoft Graph订阅的实现
问题根因
- .NET 原生不支持
Startup阶段的异步配置操作,且官方无后续支持计划。 - 若在
Startup.ConfigureServices()阶段阻塞线程执行Graph订阅创建,即使代码放在services.AddControllers()调用之后,该阶段控制器、路由、中间件管道均未完成构建,Graph API发送订阅校验请求时,应用无法返回要求的200响应,必然导致订阅注册失败。 - 最初尝试的阻塞式实现代码如下,该方案无法正常工作:
Task<Subscription> subscriptionTask = gsc.Subscriptions.Request().AddAsync(subscription); subscription = subscriptionTask.GetAwaiter().GetResult(); services.AddSingleton<GraphServiceClient>(provider => gsc); services.AddSingleton<Subscription>(provider => subscription);
已验证的变通方案
方案1:启动后手动调用接口创建
- 服务注册阶段仅注册空对象单例:
services.AddSingleton<GraphServiceClient>(provider => gsc); services.AddSingleton<Subscription>(provider => subscription); - 控制器暴露订阅创建接口,应用启动后手动调用触发:
public async Task<IActionResult> CreateMSGraphSubscription() { try { Subscription newSubscription = await _gsc.Subscriptions.Request().AddAsync(_sub); // 逐字段更新单例属性 _sub.Id = newSubscription.Id; _sub.AdditionalData = newSubscription.AdditionalData; _sub.ApplicationId = newSubscription.ApplicationId; _sub.CreatorId = newSubscription.CreatorId; _graphRenewalSettings.IsActive = true; } catch (Exception e) { return new BadRequestObjectResult(e); } return new OkObjectResult(_sub); }
- 缺陷:需要人工介入调用接口,无法实现应用启动后订阅自动就绪。
方案2:利用应用生命周期回调自动注册
在Startup.Configure()中注入IHostApplicationLifetime,在方法末尾注册应用启动完成后的回调,自动执行订阅创建:
lifetime.ApplicationStarted.Register(async () => { Subscription newSubscription = await gsc.Subscriptions.Request().AddAsync(sub); // 逐字段更新单例属性 sub.Id = newSubscription.Id; sub.AdditionalData = newSubscription.AdditionalData; sub.ApplicationId = newSubscription.ApplicationId; sub.CreatorId = newSubscription.CreatorId; graphRenewalSettings.IsActive = true; });
- 优势:无需人工调用接口,应用启动完成、管道就绪后会自动执行订阅注册,此时Graph的校验请求可以被正常响应。
- 缺陷:必须预先注册空的
Subscription单例,创建完真实订阅后逐字段拷贝属性,实现冗余,无法直接注入已完成初始化的订阅实例。
更优雅的实现方式
不需要逐字段更新单例,用IHostedService配合单例包装类即可实现直接注入可用订阅实例的需求:
- 定义一个单例级别的订阅状态包装类,内部持有
Subscription实例属性,用于存储初始化完成的订阅对象。 - 实现一个
IHostedService托管服务,在其StartAsync方法(框架原生支持异步执行,执行时机在应用启动完成、请求管道完全就绪之后)中完成Graph订阅创建,将创建好的实例赋值给包装类的Subscription属性。 - DI注册时同时注册包装类单例和托管服务,所有需要使用订阅实例的地方直接注入包装类即可,不需要做任何字段拷贝操作。
核心原则:所有需要依赖应用自身HTTP接口响应的初始化逻辑,都不能放在
ConfigureServices或Configure执行阶段运行,必须放到应用已经完成启动、开始监听请求之后执行,否则外部请求无法命中应用接口,必然导致校验失败。
内容的提问来源于stack exchange,提问作者calvinagar
相关产品推荐
相关产品推荐

