我有一个解决方案,它包含(除其他外)一个Blazor服务器端项目、三个最小的API项目、一个MAUI项目(使用最小的API)、一个Models项目(包含解决方案的模型)和一个数据访问项目。最后一个项目包含我的AppDbContext
类…
public class AppDbContext : IdentityDbContext<User> {
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) {}
// DbSets go here...
}
在Blazor和minimal API项目中作为服务注册。例如,在Blazor项目中,Program.cs
包含以下内容…
builder.Services.AddDbContext<AppDbContext>(options => {
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));
options.EnableSensitiveDataLogging();
options.EnableDetailedErrors();
}, ServiceLifetime.Transient);
builder.Services.AddDbContextFactory<AppDbContext>(lifetime: ServiceLifetime.Scoped);
请注意,我正在从普通注入(使用具有[Inject]
属性的属性)切换到使用工厂注入的过程中,这就是为什么我有上面的最后一行。这样我就可以把我的代码转换成位,而它仍然可以工作。
一切正常
我现在需要在每次数据库更新时做一些额外的日志记录。我在AppDbContext
中添加了以下内容…
public new Task<int> SaveChangesAsync(string userId = "", CancellationToken cancellationToken = default) {
// Do logging with userId
return await base.SaveChangesAsync(cancellationToken);
}
在三个最小的API项目中传递用户ID不是问题,因为我使用JWT,并且可以轻松地从令牌获取ID,并将其传递给SaveChangesAsync
(或匿名API端点的""
)。
然而,要在Blazor项目中获得登录用户的Id,我似乎需要使用AuthenticationStateProvider
。我认为将以下类添加到Blazor项目中会起作用…
public class BlazorAppDbContext : AppDbContext {
private readonly AuthenticationStateProvider? _authenticationStateProvider;
public BlazorAppDbContext(DbContextOptions<AppDbContext> options, AuthenticationStateProvider? authenticationStateProvider) : base(options) =>
_authenticationStateProvider = authenticationStateProvider;
public new Task<int> SaveChangesAsync(CancellationToken cancellationToken = default) {
// Use the AuthenticationStateProvider to get the user Id and log as before
return await base.SaveChangesAsync(cancellationToken);
}
}
我修改了Program.cs
中的代码,使用BlazorAppDbContext
而不是AppDbContext
,并试图运行,但当它击中WebApplication app = builder.Build();
时得到以下异常…
系统。AggregateException: '一些服务不能被构造
(验证服务描述符时出错)ServiceType: MyProject.Admin.Helpers. blazorappdbcontext Lifetime: Transient ImplementationType: MyProject.Admin.Helpers。无法解析类型'Microsoft.EntityFrameworkCore.DbContextOptions ' 1的服务。
(在验证服务描述符时出错)服务类型:Microsoft.AspNetCore.Identity.ISecurityStampValidator生存期:Scoped实现类型:Microsoft.AspNetCore.Identity.SecurityStampValidator1[MyProject.Models.User]': Unable to resolve service for type 'Microsoft.EntityFrameworkCore.DbContextOptions
1[myproject . dataccess . dataccess . net]。
验证服务描述符时出错服务类型:Microsoft.AspNetCore.Identity.ITwoFactorSecurityStampValidator生存期:Scoped实现类型:Microsoft.AspNetCore.Identity.TwoFactorSecurityStampValidator1[MyProject.Models.User]': Unable to resolve service for type 'Microsoft.EntityFrameworkCore.DbContextOptions
1
之后是一大堆几乎相同的异常,但针对解决方案中的其他类型。
我做了很多搜索,但所有的这种例外的答案似乎是基于人们没有正确注册服务。考虑到这与AppDbContext
工作得很好,我用BlazorAppDbContext
(扩展AppDbContext
)替换了Program.cs
中的所有实例,我真的不确定我做错了什么。
在这个答案之后,我尝试添加以下行…
builder.Services.AddHttpContextAccessor();
…但这无济于事。我并不感到惊讶,因为我已经在项目中的几个地方使用了IHttpContextAccessor
,所以如果DI容器无法解决这个问题,我早就发现了。
我试着改变构造函数如下…
public BlazorAppDbContext(DbContextOptions<BlazorAppDbContext> options, AuthenticationStateProvider? authenticationStateProvider) : base(options) =>
_authenticationStateProvider = authenticationStateProvider;
…但这会导致编译错误,因为base
取DbContextOptions<AppDbContext>
,而不是DbContextOptions<BlazorAppDbContext>
。
有人能帮忙吗?
您可以在注册BlazorAppDbContext时使用工厂方法手动构建这些选项:
builder.Services.AddTransient<BlazorAppDbContext>(sp =>
{
DbContextOptionsBuilder<AppDbContext> optionsBuilder = new();
optionsBuilder.UseSqlServer(
builder.Configuration.GetConnectionString("DefaultConnection"));
optionsBuilder.EnableSensitiveDataLogging();
optionsBuilder.EnableDetailedErrors();
return new BlazorAppDbContext(
optionsBuilder.Options,
sp.GetService<AuthenticationStateProvider>());
});
在使用:
builder.Services.AddDbContext<MyContextType>(options => ...);
则得到的DbContextOptions将是:
DbContextOptions<MyContextType>
换句话说,您不能使用AddDbContext来提供带有类型参数的DbContextOptions,而不是用于AddDbContext方法的类型参数。
这就是为什么你要用AddTransient和工厂方法代替AddDbContext,如果你想满足构造函数:
public BlazorAppDbContext(DbContextOptions<AppDbContext> options);