ASP.NET Core集成Graph API Beta遇依赖注入失败求助
基于.NET 6的ASP.NET Core应用原本可正常对接Graph API v1.0,切换至Graph API Beta版本时,已修改appsettings.json中DownstreamApi的BaseUrl为https://graph.microsoft.com/beta,并将HomeController中的Microsoft.Graph.GraphServiceClient替换为Microsoft.Graph.Beta.GraphServiceClient,适配相关模型后出现依赖注入失败错误,错误提示如下:

v1.0版本Startup.cs代码
using Azure; using Azure.Identity; using Azure.Security.KeyVault.Secrets; using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Authorization; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Identity.Web; using Microsoft.Identity.Web.UI; using System; namespace Birthway { public class Startup { public IConfiguration Configuration { get; } public Startup(IConfiguration configuration) { Configuration = configuration; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { string[] initialScopes = Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' '); services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApp(Configuration) .EnableTokenAcquisitionToCallDownstreamApi(initialScopes) .AddMicrosoftGraph(Configuration.GetSection("DownstreamApi")) .AddInMemoryTokenCaches(); // uncomment the following 3 lines to get ClientSecret from KeyVault //string tenantId = Configuration.GetValue<string>("AzureAd:TenantId"); //services.Configure<MicrosoftIdentityOptions>( // options => { options.ClientSecret = GetSecretFromKeyVault(tenantId, "ENTER_YOUR_SECRET_NAME_HERE"); }); services.AddControllersWithViews(options => { var policy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .Build(); options.Filters.Add(new AuthorizeFilter(policy)); }); services.AddRazorPages() .AddMicrosoftIdentityUI(); // Add the UI support to handle claims challenges services.AddServerSideBlazor() .AddMicrosoftIdentityConsentHandler(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); endpoints.MapRazorPages(); }); } /// Gets the secret from key vault via an enabled Managed Identity. /// </summary> /// <remarks>https://github.com/Azure-Samples/app-service-msi-keyvault-dotnet/blob/master/README.md</remarks> /// <returns></returns> private string GetSecretFromKeyVault(string tenantId, string secretName) { // this should point to your vault's URI, like https://<yourkeyvault>.vault.azure.net/ string uri = Environment.GetEnvironmentVariable("KEY_VAULT_URI"); DefaultAzureCredentialOptions options = new DefaultAzureCredentialOptions(); // Specify the tenant ID to use the dev credentials when running the app locally options.VisualStudioTenantId = tenantId; options.SharedTokenCacheTenantId = tenantId; SecretClient client = new SecretClient(new Uri(uri), new DefaultAzureCredential(options)); // The secret name, for example if the full url to the secret is https://<yourkeyvault>.vault.azure.net/secrets/ENTER_YOUR_SECRET_NAME_HERE Response<KeyVaultSecret> secret = client.GetSecretAsync(secretName).Result; return secret.Value.Value; } } }
v1.0版本HomeController.cs代码
using Azure.Identity; using Azure.Security.KeyVault.Secrets; using Azure; using Birthway.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Graph; using Microsoft.Graph.Beta; using Microsoft.Identity.Client; using Microsoft.Identity.Web; using System.Diagnostics; using Microsoft.Graph.Models; using Microsoft.Kiota.Abstractions; namespace Birthway.Controllers { [Authorize] public class HomeController : Controller { private readonly ILogger<HomeController> _logger; private readonly Microsoft.Graph.GraphServiceClient _graphServiceClient; private readonly MicrosoftIdentityConsentAndConditionalAccessHandler _consentHandler; private readonly string[] _graphScopes; public HomeController(ILogger<HomeController> logger, IConfiguration configuration, Microsoft.Graph.GraphServiceClient graphServiceClient, MicrosoftIdentityConsentAndConditionalAccessHandler consentHandler) { _logger = logger; _graphServiceClient = graphServiceClient; _consentHandler = consentHandler; _graphScopes = configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' '); } [AuthorizeForScopes(ScopeKeySection = "DownstreamApi:Scopes")] public async Task<IActionResult> Index() { User currentUser = null; try { currentUser = await _graphServiceClient.Me.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); } } try { // Get user photo using (var photoStream = await _graphServiceClient.Me.Photo.Content.GetAsync()) { byte[] photoByte = ((MemoryStream)photoStream).ToArray(); ViewData["Photo"] = Convert.ToBase64String(photoByte); } } catch (Exception pex) { Console.WriteLine($"{pex.Message}"); ViewData["Photo"] = null; } ViewData["Me"] = currentUser; return View(); } } }
修改后的HomeController.cs代码
using Birthway.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Graph; using Microsoft.Graph.Beta; using Microsoft.Identity.Client; using Microsoft.Identity.Web; using System.Diagnostics; using Microsoft.Graph.Models; using Microsoft.Kiota.Abstractions; namespace Birthway.Controllers { [Authorize] public class HomeController : Controller { private readonly ILogger<HomeController> _logger; private readonly Microsoft.Graph.Beta.GraphServiceClient _graphServiceClient; private readonly MicrosoftIdentityConsentAndConditionalAccessHandler _consentHandler; private readonly string[] _graphScopes; public HomeController(ILogger<HomeController> logger, IConfiguration configuration, Microsoft.Graph.Beta.GraphServiceClient graphServiceClient, MicrosoftIdentityConsentAndConditionalAccessHandler consentHandler) { _logger = logger; _graphServiceClient = graphServiceClient; _consentHandler = consentHandler; _graphScopes = configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' '); } [AuthorizeForScopes(ScopeKeySection = "DownstreamApi:Scopes")] public async Task<IActionResult> Index() { Microsoft.Graph.Beta.Models.User currentUser = null; try { currentUser = await _graphServiceClient.Me.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); } } try { // Get user photo using (var photoStream = await _graphServiceClient.Me.Photo.Content.GetAsync()) { byte[] photoByte = ((MemoryStream)photoStream).ToArray(); ViewData["Photo"] = Convert.ToBase64String(photoByte); } } catch (Exception pex) { Console.WriteLine($"{pex.Message}"); ViewData["Photo"] = null; } ViewData["Me"] = currentUser; return View(); } } }
问题根源
你只修改了控制器中的客户端类型,但Startup里注册的还是v1.0版本的GraphServiceClient,依赖注入容器中没有Microsoft.Graph.Beta.GraphServiceClient的实例,导致注入失败。
修复步骤
安装Beta版依赖包
在项目中安装Microsoft.Identity.Web.MicrosoftGraphBetaNuGet包,这是专门用于注册Beta版Graph客户端的扩展包:dotnet add package Microsoft.Identity.Web.MicrosoftGraphBeta修改Startup.cs的服务注册
将ConfigureServices方法中的.AddMicrosoftGraph(Configuration.GetSection("DownstreamApi"))替换为Beta版的扩展方法:services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApp(Configuration) .EnableTokenAcquisitionToCallDownstreamApi(initialScopes) // 替换为Beta版的注册方法 .AddMicrosoftGraphBeta(Configuration.GetSection("DownstreamApi")) .AddInMemoryTokenCaches();确认配置正确
确保appsettings.json中DownstreamApi的BaseUrl已设置为Beta地址:"DownstreamApi": { "BaseUrl": "https://graph.microsoft.com/beta", "Scopes": "user.read user.readbasic.all" }
完成以上步骤后,依赖注入容器会正确注册Microsoft.Graph.Beta.GraphServiceClient实例,控制器就能正常获取该服务,解决注入失败问题。
内容的提问来源于stack exchange,提问作者codeNightmare

