Чистая архитектура и Asp. Net Core Identity - PullRequest
3 голосов
/ 12 июля 2020

Я пытаюсь абстрагироваться от Asp. Net Core Identity из моего приложения, чтобы уважать Чистую архитектуру .

В настоящее время мой Проект разделен на 4 проекта: WebApi, Infrastructure, Application и Core. Я хочу, чтобы вся конфигурация Asp. Net EF Core и Asp. Net Core Identity была инкапсулирована в проект инфраструктуры. Обе службы будут доступны проекту WebApi через некоторые интерфейсы, определенные в проекте приложения (например, IApplicationDbcontext, IUserService, ICurrentUserService).

К сожалению, я не могу создать миграцию с команда диспетчера пакетов: 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;
                }
            }
        }
    }

EDIT # 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;
        }
    }

Ответы [ 2 ]

2 голосов
/ 21 июля 2020

Ваш код должен (и работает) в целом без проблем и без необходимости в производном классе IDesignTimeDbContextFactory<DbContext>.

Я загрузил минимальный проект на GitHub, который имитирует ваш дизайн и работает без проблем со следующей командой консоли диспетчера пакетов для создания миграции:

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

Куда go отсюда

Сначала возьмите посмотрите на DbContext Creation во время разработки, чтобы понять, как EF Core ищет ваш DbContext производный класс.

Затем поместите инструкции Debugger.Launch()Debugger.Break()) в свой код , чтобы запустить JIT-отладчик при выполнении команды Add-Migration.

Наконец, выполните пошаговое выполнение кода. Убедитесь, что ваш DependencyInjection.AddInfrastructure(), ApplicationDbContext.ApplicationDbContext(), ApplicationDbContext.OnModelCreating() et c. методы вызываются должным образом. неверно до того, как контекст может быть создан. Это не похоже на конструктор 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()

Один из способов справиться с этим - просто определить, вызывается ли код из EF Core Tools или нет, и настроить только необходимые службы. , если это так:

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()));
}
2 голосов
/ 19 июля 2020

Исходя из предоставленной информации, я бы сказал, что проблема в том, что некоторые зависимости, требуемые в конструкторе DbContext, не могут быть созданы поставщиком услуг (например, ICurrentUserService не зарегистрирован в предоставленном вами коде или, возможно, IDateTimeService имеет незарегистрированную зависимость).

Либо убедитесь, что все зависимости DbContext могут быть созданы, либо создайте фабрику времени разработки . Образец из связанной документации 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);
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...