干净的架构和 Asp.Net 核心标识

hla*_*nte 4 c# entity-framework-core asp.net-core clean-architecture asp.net-core-webapi

我试图从我的应用程序中抽象Asp.Net Core Identity以尊重Clean Architecture

目前,我的项目分为 4 个项目:WebApi、Infrastructure、Application 和 Core。我希望将Asp.Net EF CoreAsp.Net Core Identity 的所有配置都封装到基础设施项目中。这两个服务都将通过定义到应用程序项目中的一些接口(例如IApplicationDbcontextIUserServiceICurrentUserService)向 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) {
           ...
        }
    }
Run Code Online (Sandbox Code Playgroud)

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;
        }
    }
Run Code Online (Sandbox Code Playgroud)

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;
                }
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

编辑#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;
        }
    }

Run Code Online (Sandbox Code Playgroud)

lau*_*jpn 6

您的代码应该(并且确实)通常可以正常工作并且不需要IDesignTimeDbContextFactory<DbContext>派生类。

我将一个最小的项目上传到 GitHub,它模仿您的设计,并且使用以下包管理器控制台命令没有问题,用于创建迁移:

Add-Migration -Project "Infrastructure" -StartupProject "WebApi" -OutputDir Persistence\Migrations "Initial"

从这往哪儿走

首先,查看设计时 DbContext 创建,以了解 EF Core 如何查找您的DbContext派生类。

然后在您的代码中放入Debugger.Launch()(和Debugger.Break())指令,以在执行Add-Migration命令时触发 JIT 调试器。

最后,逐步执行您的代码。确保,你DependencyInjection.AddInfrastructure()ApplicationDbContext.ApplicationDbContext()ApplicationDbContext.OnModelCreating()越来越预期叫做等方法。

您可能还希望在调试时让您的 IDE 因任何引发的异常而中断。

您的问题可能与与 EF Core 完全无关的事情有关,在可以实例化上下文之前出现问题。它似乎不是CurrentUserService构造函数,但它可能是IDateTimeService实现类的构造函数或在初始化过程中运行的其他东西。你应该能够找出什么时候单步抛出代码。

更新:问题和解决方案

正如预期的那样,该问题与 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()
Run Code Online (Sandbox Code Playgroud)

处理此问题的一种方法是仅检测是否从 EF Core Tools 调用代码,并仅设置必要的服务(如果是这种情况):

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()
Run Code Online (Sandbox Code Playgroud)