I'm trying to abstract Asp.Net Core Identity from my Application in order to respect a Clean Architecture.
Currently, my project is divided into 4 projects : WebApi, Infrastructure, Application and Core. I want all configuration of Asp.Net EF Core and Asp.Net Core Identity to be encapsulate into the Infrastructure project. Both services will be exposed to the WebApi project by some interfaces defined into the Application Project (e.g. IApplicationDbcontext
, IUserService
, ICurrentUserService
).
Unfortunetely, I'm unable to create a migration with the package manager command : Add-Migration -Project src\Infrastructure -StartupProject src\WebApi -OutputDir Persistence\Migrations "SmartCollaborationDb_V1"
.
Error : Unable to create an object of type 'ApplicationDbContext'. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728
.
Can you help me?
Solution Structure
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;
}
}