In the Project I´m currently working at, I need to audit the changes to many database tables. To do so I´m considering working with the nuget Z.EntityFramework.Plus.Audit.EFCore. I´ve been experimenting and it seems to mostly fit my needs. I´ve been following the docs here.
It is possible to save audit entries in my controller like so:
public myFunction(){
//...
var audit = new Audit();
_context.SaveChanges(audit);
}
But if I do it in such manner, it would be breaking the DRY principle. There is a way to override the SaveChanges() and SaveChagesAsync() functions, and it works wonders. Such thing is done like this:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
{
AuditManager.DefaultConfiguration.AutoSavePreAction = (context, audit) =>
// ADD "Where(x => x.AuditEntryID == 0)" to allow multiple SaveChanges with same Audit
(context as ApplicationDbContext).AuditEntries.AddRange(audit.Entries);
}
public override int SaveChanges()
{
var audit = new Audit { }; //Problem would be here
audit.PreSaveChanges(this);
var rowAffecteds = base.SaveChanges();
audit.PostSaveChanges();
if (audit.Configuration.AutoSavePreAction != null)
{
audit.Configuration.AutoSavePreAction(this, audit);
base.SaveChanges();
}
return rowAffecteds;
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
var audit = new Audit { }; //Problem would be here
audit.PreSaveChanges(this);
var rowAffecteds = await base.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
audit.PostSaveChanges();
if (audit.Configuration.AutoSavePreAction != null)
{
audit.Configuration.AutoSavePreAction(this, audit);
await base.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
}
return rowAffecteds;
}
//... DbContext Code
}
Now that is a way better option, very DRY and more mantainable. Problem is, how to get the current username so that i can do something like
var audit = new Audit { CreatedBy = "my_current_username"};
In the controller you can use the instruction
var username = this.User.Identity.Name;
but this is not available in the IdentityDbContext. I did try injecting UserManager, but it didn´t have a function that might work.
tl;dr; How do I get the current UserName in IdentityDbContext when I´m using JWT tokens?
In ConfigureServices
:
services.AddHttpContextAccessor();
services.AddDbContext<FooContext>((provider, options) =>
options.UseSqlServer(Configuration.GetConnectionString("Foo"))
.UseInternalServiceProvider(provider);
In FooContext.SaveChanges
/FooContext.SaveChangesAsync
:
var httpContextAccessor = this.GetService<IHttpContextAccesor>();
var userName = httpContextAccessor.HttpContext?.User.Identity.Name;
if (userName != null)
{
// You've got the username. Do something with it.
}