1
votes

I am quite new to ASP.NET Core. I want to create ASP.NET Core 2.2 WebAPI, with Entity Framework Core and ASP.NET Identity. Also, I want to use JWT to secure access to that WebAPI with tokens.

I have successfully managed to insert user to DB and to get token back from my API, and token auth is working.

But, I also want to soft delete database records and to keep track on who has deleted/updated/added a record. Test table for that is Countries, where fields are only Title and Description.

So far, I have turned Internet upside-down, but no help :(

Las thing that I have tried is (userId is null):

  • In startup.cs:

    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    
  • in my AppDbContext.cs

    public class AppDbContext : IdentityDbContext<AppUser>{
    
        public AppDbContext(DbContextOptions options) : base(options)
        {
        }
    
        public DbSet<AppUser> AppUsers { get; set; }
    
        public DbSet<Country> Countries { get; set; } // just for testing
    
        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);
    
            // TODO - IGNORE THIS
            //builder.Entity<AppUser>().Property<bool>("IsDeleted");
            //builder.Entity<AppUser>().HasQueryFilter(m => EF.Property<bool>(m, "IsDeleted") == false);
        }
    
        public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            CheckBeforeSaving();
            return (await base.SaveChangesAsync(true, cancellationToken).ConfigureAwait(false));
        }
    
        private void CheckBeforeSaving()
        {
            var httpContextAccessor = this.GetService<IHttpContextAccessor>();
            var userId = httpContextAccessor?.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
    
            foreach (var entry in ChangeTracker.Entries())
            {
                switch (entry.State)
                {
                    case EntityState.Detached:
                        break;
                    case EntityState.Unchanged:
                        break;
                    case EntityState.Deleted:
                        entry.State = EntityState.Modified;
                        entry.CurrentValues["IsDeleted"] = true;
                        entry.CurrentValues["DeletedBy"] = userId;
                        entry.CurrentValues["Deleted"] = DateTime.Now;
                        break;
                    case EntityState.Modified:
                        entry.CurrentValues["UpdatedBy"] = userId;
                        entry.CurrentValues["Updated"] = DateTime.Now;
                        break;
                    case EntityState.Added:
                        entry.CurrentValues["IsDeleted"] = false;
                        entry.CurrentValues["CreatedBy"] = userId;
                        entry.CurrentValues["Created"] = DateTime.Now;
                        entry.CurrentValues["Description"] = userId;
                        break;
                    default:
                        break;
                }
            }
        }}}
    
3
i don't understand your question. If you have a usertable, appuser, then retrieving the id should not be any more difficult than other ids. If you have one user who is logged in, or has access to a method, then you have the information to look him up and get the id. then you could have another table for loggin, so when he performs an action you save this to the log along with his id and a description of his actions. But maybe i'm missing something here, because i'm not familiar with using httpcontextaccessor like you do. - Johan Herstad
@Johan I am able to get userId inside controllers by using ClaimsIdentity id = identity.Claims.Single(c => c.Type == "id").Value but I cannot manage to get it inside my AppDbContext part of app - leroy
it's interesting. But i don't understand why you would want to do it that way. You can keep track of all those things by loggin them into another table as you perform those actions. i.e: if(_context.Entry(objectToDelete).State == EntityState.Detached) { dbSet.Attach(objectToDelete); } dbSet.Remove(objectToDelete); then create a table to logg this action: var logg = new Logg { userId = xxx, info = "username deleted country", date = xxx etc }. _context.Log.Add(log); then _context.SaveChangesAsync(); - Johan Herstad
I do not HAVE to do it this way, I just need to keep track on who is doing what. I just thought it is convenient to keep track of that in each table, and not in one table for all. Can you provide me with help of working example of your suggestion? - leroy

3 Answers

1
votes

Try to inject IHttpContextAccessor into the ApplicationDbContext directly:

public class ApplicationDbContext : IdentityDbContext<AppUser>
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, IHttpContextAccessor httpContextAccessor)
        : base(options)
    {
        _httpContextAccessor= httpContextAccessor;
    }

    //other methods

   private void CheckBeforeSaving()
   {        
     var userId = _httpContextAccessor?.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
     //...
   }
}
0
votes

Here is how I'm getting user id in .net core 2.2

First you need to register in your Startup.cs

services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();

Then in your service

private readonly IHttpContextAccessor _httpContextAccessor;

public UserService(
    IHttpContextAccessor httpContextAccessor)
{

}

var currentUserGuid = _httpContextAccessor?.HttpContext?.User?.FindFirst(UserClaimsKey.Sub)?.Value;
-1
votes
private async Task<ApplicationUser> GetUserDetailsAnsyc()
{
  return await userManager.GetUserAnsync(HttpContext.User);
}

var user = await GetUserDetailsAnsyc();
Console.WriteLine(user.Id.ToString());