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

.NET7 Blazor WASM中避免BuildServiceProvider警告的服务访问方案

问题描述

我有一个Blazor WASM项目,使用外部Identity Server服务完成用户认证与登录。在用户完成认证、重定向至Blazor应用之前,需要查询并填充一系列应用专属的claims。目前在服务器端Program.cs文件中,通过AddOpenIdConnect提供的OnUserInformationReceived事件实现该逻辑,但代码中using (ServiceProvider serviceProvider = builder.Services.BuildServiceProvider())处收到如下警告:

Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created. Consider alternatives such as dependency injecting services as parameters to 'Configure'.

使用框架为.NET7,完整Program.cs代码如下:

public partial class Program
{
    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);

        var CommonConnectionString = builder.Configuration.GetConnectionString("CommonConnection");
        builder.Services.AddDbContext<CommonDbContext>(options =>
        {
            options.UseSqlServer(CommonConnectionString);
            options.EnableSensitiveDataLogging(true);
        });

        builder.Services.AddControllersWithViews();
        builder.Services.AddRazorPages();

        builder.Services.AddBff().AddServerSideSessions();

        builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
        builder.Services.AddScoped<Hermes.Services.User.IUserService, UserService>();

        builder.Services.AddAuthentication(options =>
        {
            options.DefaultScheme = "cookie";
            options.DefaultChallengeScheme = "oidc";
            options.DefaultSignOutScheme = "oidc";
        })
            .AddCookie("cookie", options =>
            {
                options.Cookie.Name = "__Host-blazor";
                options.Cookie.SameSite = SameSiteMode.Strict;
                options.ExpireTimeSpan = TimeSpan.FromMinutes(15);
                options.SlidingExpiration = false;
            })
            .AddOpenIdConnect("oidc", options =>
            {
                options.Authority = "https://localhost:5001";

                options.ClientId = "bff";
                options.ClientSecret = "secret";
                options.ResponseType = "code";
                options.ResponseMode = "query";

                options.Scope.Clear();
                options.Scope.Add("openid");
                options.Scope.Add("profile");
                options.Scope.Add("offline_access");
                options.Scope.Add("email");

                options.MapInboundClaims = false;
                options.GetClaimsFromUserInfoEndpoint = true;
                options.SaveTokens = true;

                options.Events = new OpenIdConnectEvents
                {
                    OnUserInformationReceived = async ctx =>
                    {
                        var sub = ctx.Principal.Claims.FirstOrDefault(c => c.Type == "sub").Value;

                        using (ServiceProvider serviceProvider = builder.Services.BuildServiceProvider())
                        {
                            var userService = serviceProvider.GetRequiredService<Hermes.Services.User.IUserService>();

                            var claims = await userService.GetUserDetails(sub);

                            var appIdentity = new ClaimsIdentity(claims);

                            ctx.Principal.AddIdentity(appIdentity);
                        }

                    }
                };
            });

        var QualityAssuranceConnectionString = builder.Configuration.GetConnectionString("QualityAssuranceConnection");
        builder.Services.AddDbContext<QualityAssuranceDbContext>(options =>
        {
            options.UseSqlServer(QualityAssuranceConnectionString);
            options.EnableSensitiveDataLogging(true);
        });
        builder.Services.AddScoped<IUnitOfWorkQA, UnitOfWorkQA>();
        builder.Services.AddScoped<IQualityAssuranceService, QualityAssuranceService>();

        var app = builder.Build();

        // Configure the HTTP request pipeline.
        if (app.Environment.IsDevelopment())
        {
            app.UseWebAssemblyDebugging();
        }
        else
        {
            app.UseExceptionHandler("/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.UseBlazorFrameworkFiles();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthentication();
        app.UseBff();
        app.UseAuthorization();

        app.MapBffManagementEndpoints();

        app.MapRazorPages();
        app.MapControllers();
        app.MapFallbackToFile("index.html");

        app.Run();
    }
}

优化方案

方案1:直接使用HttpContext内置服务容器

OnUserInformationReceived上下文的HttpContext已经提供了请求级的服务容器,无需手动构建ServiceProvider,直接获取所需服务即可:

修改后的OnUserInformationReceived事件代码:

options.Events = new OpenIdConnectEvents
{
    OnUserInformationReceived = async ctx =>
    {
        var sub = ctx.Principal.Claims.FirstOrDefault(c => c.Type == "sub")?.Value;
        if (string.IsNullOrEmpty(sub)) return;

        // 从HttpContext获取已初始化的服务实例
        var userService = ctx.HttpContext.RequestServices.GetRequiredService<Hermes.Services.User.IUserService>();
        var claims = await userService.GetUserDetails(sub);

        var appIdentity = new ClaimsIdentity(claims);
        ctx.Principal.AddIdentity(appIdentity);
    }
};

这种方式完全遵循依赖注入最佳实践,不会创建额外的单例服务副本,直接消除警告,同时代码更简洁。

方案2:抽离事件处理类(解耦实现)

如果希望事件逻辑与Program.cs解耦,可将事件处理逻辑封装为独立类,通过构造函数注入所需服务:

1. 创建自定义事件处理类

public class CustomOpenIdConnectEvents : OpenIdConnectEvents
{
    private readonly Hermes.Services.User.IUserService _userService;

    // 构造函数注入依赖
    public CustomOpenIdConnectEvents(Hermes.Services.User.IUserService userService)
    {
        _userService = userService;
    }

    public override async Task UserInformationReceived(UserInformationReceivedContext context)
    {
        var sub = context.Principal.Claims.FirstOrDefault(c => c.Type == "sub")?.Value;
        if (string.IsNullOrEmpty(sub)) return;

        var claims = await _userService.GetUserDetails(sub);
        var appIdentity = new ClaimsIdentity(claims);
        context.Principal.AddIdentity(appIdentity);

        await base.UserInformationReceived(context);
    }
}

2. 注册事件处理类

在Program.cs的服务注册区域添加:

builder.Services.AddScoped<CustomOpenIdConnectEvents>();

3. 在AddOpenIdConnect中使用该类

修改AddOpenIdConnect的配置代码,改为使用注入的事件处理类:

.AddOpenIdConnect("oidc", (services, options) =>
{
    // 原有配置保持不变
    options.Authority = "https://localhost:5001";
    options.ClientId = "bff";
    options.ClientSecret = "secret";
    options.ResponseType = "code";
    options.ResponseMode = "query";

    options.Scope.Clear();
    options.Scope.Add("openid");
    options.Scope.Add("profile");
    options.Scope.Add("offline_access");
    options.Scope.Add("email");

    options.MapInboundClaims = false;
    options.GetClaimsFromUserInfoEndpoint = true;
    options.SaveTokens = true;

    // 使用注入的自定义事件处理类
    options.Events = services.GetRequiredService<CustomOpenIdConnectEvents>();
});

这种方式将事件逻辑与启动配置分离,符合单一职责原则,便于后续维护和扩展。


内容的提问来源于stack exchange,提问作者Hannah Hayes

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 15:37:09