干净的架构和 Asp.Net 核心标识
Clean Architecture and Asp.Net Core Identity
我正在尝试从我的应用程序中提取 Asp.Net 核心身份 以尊重干净的架构。
目前,我的项目分为4个项目:WebApi、Infrastructure、Application和Core。我希望 Asp.Net EF Core 和 Asp.Net Core Identity 的所有配置都封装到基础设施项目中。这两种服务都将通过应用程序项目中定义的一些接口(例如 IApplicationDbcontext
、IUserService
、ICurrentUserService
)暴露给 WebApi 项目。
不幸的是,我无法使用包管理器命令创建迁移:Add-Migration -Project src\Infrastructure -StartupProject src\WebApi -OutputDir Persistence\Migrations "SmartCollaborationDb_V1"
。
错误:Unable to create an object of type 'ApplicationDbContext'. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728
.
你能帮帮我吗?
解决方案结构
src\WebApi\Startup.cs
public class Startup {
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration) {
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services) {
services.AddApplication(Configuration);
services.AddInfrastructure(Configuration);
services.AddHttpContextAccessor();
...
services.AddScoped<ICurrentUserService, CurrentUserService>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
...
}
}
src\Infrastructure\DependencyInjection.cs
public static class DependencyInjection {
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration config) {
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
config.GetConnectionString("DefaultConnection"),
context => context.MigrationsAssembly(Assembly.GetExecutingAssembly().FullName)));
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddScoped<IApplicationDbContext>(provider => provider.GetService<ApplicationDbContext>());
services.AddTransient<IDateTimeService, DateTimeService>();
services.AddTransient<IUserService, UserService>();
return services;
}
}
src\Infrastructure\Persistence\ApplicationDbContext.cs
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, Guid>, IApplicationDbContext {
private readonly ICurrentUserService currentUserService;
private readonly IDateTimeService dateTimeService;
public DbSet<Student> Students { get; set; }
public DbSet<Group> Groups { get; set; }
public DbSet<Course> Courses { get; set; }
public ApplicationDbContext(
DbContextOptions options,
ICurrentUserService currentUserService,
IDateTimeService dateTimeService) :
base(options) {
this.currentUserService = currentUserService;
this.dateTimeService = dateTimeService;
}
protected override void OnModelCreating(ModelBuilder builder) {
builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
base.OnModelCreating(builder);
}
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default) {
UpdateAuditableEntities();
return base.SaveChangesAsync(cancellationToken);
}
private void UpdateAuditableEntities() {
foreach (var entry in ChangeTracker.Entries<AuditableEntity>()) {
switch (entry.State) {
case EntityState.Added:
entry.Entity.CreatedBy = currentUserService.UserId.ToString();
entry.Entity.Created = dateTimeService.Now;
break;
case EntityState.Modified:
entry.Entity.LastModifiedBy = currentUserService.UserId.ToString();
entry.Entity.LastModified = dateTimeService.Now;
break;
}
}
}
}
编辑#01
src\WebApi\Services\CurrentUserService.cs
public class CurrentUserService : ICurrentUserService {
public Guid UserId { get; }
public bool IsAuthenticated { get; }
public CurrentUserService(IHttpContextAccessor httpContextAccessor) {
var claim = httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier);
IsAuthenticated = claim != null;
UserId = IsAuthenticated ? Guid.Parse(claim) : Guid.Empty;
}
}
根据提供的信息,我认为问题是服务提供商无法创建 DbContext 的构造函数中所需的某些依赖项(例如 ICurrentUserService
未在您提供的代码中注册或也许 IDateTimeService
有一个未注册的依赖项。
要么确保可以创建 DbContext 的所有依赖项,要么创建 design-time factory。来自链接的 Microsoft 文档的示例:
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace MyProject
{
public class BloggingContextFactory : IDesignTimeDbContextFactory<BloggingContext>
{
public BloggingContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
optionsBuilder.UseSqlite("Data Source=blog.db");
return new BloggingContext(optionsBuilder.Options);
}
}
}
您的代码应该(并且确实)通常可以正常工作,并且不需要 IDesignTimeDbContextFactory<DbContext>
派生 class。
我上传了一个 minimal project 到 GitHub,它模仿了您的设计,并且可以毫无问题地使用以下包管理器控制台命令来创建迁移:
Add-Migration -Project "Infrastructure" -StartupProject "WebApi" -OutputDir Persistence\Migrations "Initial"
从这里去哪里
首先,查看 Design-time DbContext Creation,了解 EF Core 如何查找您的 DbContext
派生的 class。
然后在你的代码中放入Debugger.Launch()
(和Debugger.Break()
)指令,在执行Add-Migration
命令时触发JIT调试器。
最后,单步执行您的代码。确保您的 DependencyInjection.AddInfrastructure()
、ApplicationDbContext.ApplicationDbContext()
、ApplicationDbContext.OnModelCreating()
等方法按预期被调用。
您可能还想让 IDE 在调试时中断任何引发的异常。
您的问题可能与 EF Core 完全无关的问题有关,在实例化上下文之前出现问题。它似乎不是 CurrentUserService
构造函数,但它可能是 IDateTimeService
实现 class 的构造函数或在初始化过程中运行的其他东西。
步进抛出代码的时候应该可以发现。
更新:问题与解决方案
不出所料,该问题与 EF Core 无关。 AddFluentValidation()
方法抛出以下异常:
System.NotSupportedException: The invoked member is not supported in a dynamic assembly.
at at System.Reflection.Emit.InternalAssemblyBuilder.GetExportedTypes()
at FluentValidation.AssemblyScanner.FindValidatorsInAssembly(Assembly assembly) in /home/jskinner/code/FluentValidation/src/FluentValidation/AssemblyScanner.cs:49
at FluentValidation.ServiceCollectionExtensions.AddValidatorsFromAssembly(IServiceCollection services, Assembly assembly, ServiceLifetime lifetime) in /home/jskinner/code/FluentValidation/src/FluentValidation.DependencyInjectionExtensions/ServiceCollectionExtensions.cs:48
at FluentValidation.ServiceCollectionExtensions.AddValidatorsFromAssemblies(IServiceCollection services, IEnumerable`1 assemblies, ServiceLifetime lifetime) in /home/jskinner/code/FluentValidation/src/FluentValidation.DependencyInjectionExtensions/ServiceCollectionExtensions.cs:35
at FluentValidation.AspNetCore.FluentValidationMvcExtensions.AddFluentValidation(IMvcBuilder mvcBuilder, Action`1 configurationExpression) in /home/jskinner/code/FluentValidation/src/FluentValidation.AspNetCore/FluentValidationMvcExtensions.cs:72
at WebApi.Startup.ConfigureServices(IServiceCollection services) in E:\Sources\SmartCollaboration\WebApi\Startup.cs:52
at at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
at at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at at Microsoft.AspNetCore.Hosting.ConfigureServicesBuilder.InvokeCore(Object instance, IServiceCollection services)
at at Microsoft.AspNetCore.Hosting.ConfigureServicesBuilder.<>c__DisplayClass9_0.<Invoke>g__Startup|0(IServiceCollection serviceCollection)
at at Microsoft.AspNetCore.Hosting.ConfigureServicesBuilder.Invoke(Object instance, IServiceCollection services)
at at Microsoft.AspNetCore.Hosting.ConfigureServicesBuilder.<>c__DisplayClass8_0.<Build>b__0(IServiceCollection services)
at at Microsoft.AspNetCore.Hosting.GenericWebHostBuilder.UseStartup(Type startupType, HostBuilderContext context, IServiceCollection services)
at at Microsoft.AspNetCore.Hosting.GenericWebHostBuilder.<>c__DisplayClass12_0.<UseStartup>b__0(HostBuilderContext context, IServiceCollection services)
at at Microsoft.Extensions.Hosting.HostBuilder.CreateServiceProvider()
at at Microsoft.Extensions.Hosting.HostBuilder.Build()
处理此问题的一种方法是仅检测是否从 EF Core 工具调用代码,并仅设置必要的服务,如果是的话:
public void ConfigureServices(IServiceCollection services)
{
Debugger.Launch(); // <-- Remove this after debugging!
services.AddApplication(Configuration);
services.AddInfrastructure(Configuration);
services.AddScoped<ICurrentUserService, CurrentUserService>();
if (new StackTrace()
.GetFrames()
.Any(f => f?.GetMethod()?.DeclaringType?.Namespace == "Microsoft.EntityFrameworkCore.Tools"))
{
// Called by EF Core design-time tools.
// No need to initialize further.
return;
}
services.AddSwaggerGen(options => {
options.SwaggerDoc("v1", new OpenApiInfo {
Version = "v1",
Title = "SmartCollaboration API"
});
options.AddFluentValidationRules();
});
services.AddHttpContextAccessor();
services.AddControllers().AddFluentValidation(options =>
options.RegisterValidatorsFromAssemblies(AppDomain.CurrentDomain.GetAssemblies()));
}
我正在尝试从我的应用程序中提取 Asp.Net 核心身份 以尊重干净的架构。
目前,我的项目分为4个项目:WebApi、Infrastructure、Application和Core。我希望 Asp.Net EF Core 和 Asp.Net Core Identity 的所有配置都封装到基础设施项目中。这两种服务都将通过应用程序项目中定义的一些接口(例如 IApplicationDbcontext
、IUserService
、ICurrentUserService
)暴露给 WebApi 项目。
不幸的是,我无法使用包管理器命令创建迁移:Add-Migration -Project src\Infrastructure -StartupProject src\WebApi -OutputDir Persistence\Migrations "SmartCollaborationDb_V1"
。
错误:Unable to create an object of type 'ApplicationDbContext'. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728
.
你能帮帮我吗?
解决方案结构
src\WebApi\Startup.cs
public class Startup {
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration) {
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services) {
services.AddApplication(Configuration);
services.AddInfrastructure(Configuration);
services.AddHttpContextAccessor();
...
services.AddScoped<ICurrentUserService, CurrentUserService>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
...
}
}
src\Infrastructure\DependencyInjection.cs
public static class DependencyInjection {
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration config) {
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
config.GetConnectionString("DefaultConnection"),
context => context.MigrationsAssembly(Assembly.GetExecutingAssembly().FullName)));
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddScoped<IApplicationDbContext>(provider => provider.GetService<ApplicationDbContext>());
services.AddTransient<IDateTimeService, DateTimeService>();
services.AddTransient<IUserService, UserService>();
return services;
}
}
src\Infrastructure\Persistence\ApplicationDbContext.cs
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, Guid>, IApplicationDbContext {
private readonly ICurrentUserService currentUserService;
private readonly IDateTimeService dateTimeService;
public DbSet<Student> Students { get; set; }
public DbSet<Group> Groups { get; set; }
public DbSet<Course> Courses { get; set; }
public ApplicationDbContext(
DbContextOptions options,
ICurrentUserService currentUserService,
IDateTimeService dateTimeService) :
base(options) {
this.currentUserService = currentUserService;
this.dateTimeService = dateTimeService;
}
protected override void OnModelCreating(ModelBuilder builder) {
builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
base.OnModelCreating(builder);
}
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default) {
UpdateAuditableEntities();
return base.SaveChangesAsync(cancellationToken);
}
private void UpdateAuditableEntities() {
foreach (var entry in ChangeTracker.Entries<AuditableEntity>()) {
switch (entry.State) {
case EntityState.Added:
entry.Entity.CreatedBy = currentUserService.UserId.ToString();
entry.Entity.Created = dateTimeService.Now;
break;
case EntityState.Modified:
entry.Entity.LastModifiedBy = currentUserService.UserId.ToString();
entry.Entity.LastModified = dateTimeService.Now;
break;
}
}
}
}
编辑#01
src\WebApi\Services\CurrentUserService.cs
public class CurrentUserService : ICurrentUserService {
public Guid UserId { get; }
public bool IsAuthenticated { get; }
public CurrentUserService(IHttpContextAccessor httpContextAccessor) {
var claim = httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier);
IsAuthenticated = claim != null;
UserId = IsAuthenticated ? Guid.Parse(claim) : Guid.Empty;
}
}
根据提供的信息,我认为问题是服务提供商无法创建 DbContext 的构造函数中所需的某些依赖项(例如 ICurrentUserService
未在您提供的代码中注册或也许 IDateTimeService
有一个未注册的依赖项。
要么确保可以创建 DbContext 的所有依赖项,要么创建 design-time factory。来自链接的 Microsoft 文档的示例:
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace MyProject
{
public class BloggingContextFactory : IDesignTimeDbContextFactory<BloggingContext>
{
public BloggingContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
optionsBuilder.UseSqlite("Data Source=blog.db");
return new BloggingContext(optionsBuilder.Options);
}
}
}
您的代码应该(并且确实)通常可以正常工作,并且不需要 IDesignTimeDbContextFactory<DbContext>
派生 class。
我上传了一个 minimal project 到 GitHub,它模仿了您的设计,并且可以毫无问题地使用以下包管理器控制台命令来创建迁移:
Add-Migration -Project "Infrastructure" -StartupProject "WebApi" -OutputDir Persistence\Migrations "Initial"
从这里去哪里
首先,查看 Design-time DbContext Creation,了解 EF Core 如何查找您的 DbContext
派生的 class。
然后在你的代码中放入Debugger.Launch()
(和Debugger.Break()
)指令,在执行Add-Migration
命令时触发JIT调试器。
最后,单步执行您的代码。确保您的 DependencyInjection.AddInfrastructure()
、ApplicationDbContext.ApplicationDbContext()
、ApplicationDbContext.OnModelCreating()
等方法按预期被调用。
您可能还想让 IDE 在调试时中断任何引发的异常。
您的问题可能与 EF Core 完全无关的问题有关,在实例化上下文之前出现问题。它似乎不是 CurrentUserService
构造函数,但它可能是 IDateTimeService
实现 class 的构造函数或在初始化过程中运行的其他东西。
步进抛出代码的时候应该可以发现。
更新:问题与解决方案
不出所料,该问题与 EF Core 无关。 AddFluentValidation()
方法抛出以下异常:
System.NotSupportedException: The invoked member is not supported in a dynamic assembly.
at at System.Reflection.Emit.InternalAssemblyBuilder.GetExportedTypes()
at FluentValidation.AssemblyScanner.FindValidatorsInAssembly(Assembly assembly) in /home/jskinner/code/FluentValidation/src/FluentValidation/AssemblyScanner.cs:49
at FluentValidation.ServiceCollectionExtensions.AddValidatorsFromAssembly(IServiceCollection services, Assembly assembly, ServiceLifetime lifetime) in /home/jskinner/code/FluentValidation/src/FluentValidation.DependencyInjectionExtensions/ServiceCollectionExtensions.cs:48
at FluentValidation.ServiceCollectionExtensions.AddValidatorsFromAssemblies(IServiceCollection services, IEnumerable`1 assemblies, ServiceLifetime lifetime) in /home/jskinner/code/FluentValidation/src/FluentValidation.DependencyInjectionExtensions/ServiceCollectionExtensions.cs:35
at FluentValidation.AspNetCore.FluentValidationMvcExtensions.AddFluentValidation(IMvcBuilder mvcBuilder, Action`1 configurationExpression) in /home/jskinner/code/FluentValidation/src/FluentValidation.AspNetCore/FluentValidationMvcExtensions.cs:72
at WebApi.Startup.ConfigureServices(IServiceCollection services) in E:\Sources\SmartCollaboration\WebApi\Startup.cs:52
at at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
at at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at at Microsoft.AspNetCore.Hosting.ConfigureServicesBuilder.InvokeCore(Object instance, IServiceCollection services)
at at Microsoft.AspNetCore.Hosting.ConfigureServicesBuilder.<>c__DisplayClass9_0.<Invoke>g__Startup|0(IServiceCollection serviceCollection)
at at Microsoft.AspNetCore.Hosting.ConfigureServicesBuilder.Invoke(Object instance, IServiceCollection services)
at at Microsoft.AspNetCore.Hosting.ConfigureServicesBuilder.<>c__DisplayClass8_0.<Build>b__0(IServiceCollection services)
at at Microsoft.AspNetCore.Hosting.GenericWebHostBuilder.UseStartup(Type startupType, HostBuilderContext context, IServiceCollection services)
at at Microsoft.AspNetCore.Hosting.GenericWebHostBuilder.<>c__DisplayClass12_0.<UseStartup>b__0(HostBuilderContext context, IServiceCollection services)
at at Microsoft.Extensions.Hosting.HostBuilder.CreateServiceProvider()
at at Microsoft.Extensions.Hosting.HostBuilder.Build()
处理此问题的一种方法是仅检测是否从 EF Core 工具调用代码,并仅设置必要的服务,如果是的话:
public void ConfigureServices(IServiceCollection services)
{
Debugger.Launch(); // <-- Remove this after debugging!
services.AddApplication(Configuration);
services.AddInfrastructure(Configuration);
services.AddScoped<ICurrentUserService, CurrentUserService>();
if (new StackTrace()
.GetFrames()
.Any(f => f?.GetMethod()?.DeclaringType?.Namespace == "Microsoft.EntityFrameworkCore.Tools"))
{
// Called by EF Core design-time tools.
// No need to initialize further.
return;
}
services.AddSwaggerGen(options => {
options.SwaggerDoc("v1", new OpenApiInfo {
Version = "v1",
Title = "SmartCollaboration API"
});
options.AddFluentValidationRules();
});
services.AddHttpContextAccessor();
services.AddControllers().AddFluentValidation(options =>
options.RegisterValidatorsFromAssemblies(AppDomain.CurrentDomain.GetAssemblies()));
}