0

In ASP.NET Core-6 Web API, I am implementing Entity Framework and ASP.NET Identity Db Context.

I have these services.

Interface:

public interface ICurrentUserService
{
    public string UserId { get; }
    public string UserName { get; }
}

Implementation:

public class CurrentUserService : ICurrentUserService
{
    public string UserId { get; }
    public string UserName { get; }

    public CurrentUserService(IHttpContextAccessor httpContextAccessor)
    {
        UserName = httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.Name);
        UserId = httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier);
    }
}

Then I called it in ApplicationDbContext as shown below.

ApplicationDbContext:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, string, IdentityUserClaim<string>,ApplicationUserRole, IdentityUserLogin<string>,IdentityRoleClaim<string>, IdentityUserToken<string>>
{
    private readonly ICurrentUserService _currentUserService;
    private readonly IDateTime _dateTime;
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
    {
    }
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options,
        ICurrentUserService currentUserService, IDateTime dateTime)
        : base(options)
    {
        _currentUserService = currentUserService;
        _dateTime = dateTime;
    }
    public DbSet<ApplicationUser> ApplicationUsers { get; set; }
    public DbSet<ApplicationRole> ApplicationRoles { get; set; }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);
        base.OnModelCreating(builder);

        builder.ApplyConfiguration(new ApplicationUserConfigurations());
        builder.ApplyConfiguration(new ApplicationUserRoleConfigurations());
    }

    public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
    {
        foreach (var item in ChangeTracker.Entries<AuditableEntity>())
        {
            switch (item.State)
            {
                case EntityState.Modified:
                    item.Entity.LastModifiedBy = _currentUserService?.UserName;
                    item.Entity.LastModifiedAt = DateTime.Now;
                    break;

                case EntityState.Added:
                    item.Entity.CreatedBy = _currentUserService?.UserName;
                    item.Entity.CreatedAt = DateTime.Now;
                    break;

                case EntityState.Deleted:
                    item.Entity.DeletedBy = _currentUserService?.UserName;
                    item.Entity.DeletedAt = DateTime.Now;
                    item.Entity.IsDeleted = true;
                    break;

                default:
                    break;
            }
        }
        return await base.SaveChangesAsync(cancellationToken);
    }

    public override int SaveChanges()
    {
        OnBeforeSaveChanges(_currentUserService?.UserName);
        foreach (var item in ChangeTracker.Entries<AuditableEntity>())
        {
            switch (item.State)
            {
                case EntityState.Modified:
                    item.Entity.LastModifiedBy = _currentUserService?.UserName;
                    item.Entity.LastModifiedAt = DateTime.Now;
                    break;

                case EntityState.Added:
                    item.Entity.CreatedBy = _currentUserService?.UserName;
                    item.Entity.CreatedAt = DateTime.Now;
                    break;

                case EntityState.Deleted:
                    item.Entity.DeletedBy = _currentUserService?.UserName;
                    item.Entity.DeletedAt = DateTime.Now;
                    item.Entity.IsDeleted = true;
                    break;

                default:
                    break;
            }
        }
        return base.SaveChanges();
    }
}

UnitOfWork:

public class UnitOfWork : IUnitOfWork
{
    private readonly ApplicationDbContext _dbContext;

    public UnitOfWork(
        ApplicationDbContext dbContext,
        UserManager<ApplicationUser> userManager
        )
    {
        _dbContext = dbContext;
        _userManager = userManager;
    }

    public async Task Save()
    {
        await _dbContext.SaveChangesAsync();
    }
}

In the application, when I implement it as:

 await _unitOfWork.Employees.InsertAsync(bankUser);
 await _unitOfWork.Save();

I expected it to also insert UserName in the CreatedBy field, but it is null.

But If I implement it directly as :

_currentUserService.UserName

inside the application, it works..

What could be wrong and how do I correct this?

Thanks

2
  • It will only work where HttpContext is present otherwise it will be null. Commented Sep 21, 2022 at 6:33
  • @Eldar - How do I achieve that? I need to add it from the ApplicationDbContext. Is there any alternative way? Commented Sep 21, 2022 at 6:36

1 Answer 1

0

I think the problem is that you assign UserName and UserId right away when the service is constructed. At the time of the construction of your service there might not be a HttpContext to work with.

Try using the HttpContextAccessor in your getter like this:


public class CurrentUserService : ICurrentUserService
{
    private readonly IHttpContextAccessor httpContextAccessor;

    public CurrentUserService(IHttpContextAccessor httpContextAccessor)
    {
        this.httpContextAccessor = httpContextAccessor;
    }

    public string UserName => this.httpContextAccessor.HttpContext?.User?.Identity?.Name;

    public string UserId => this.httpContextAccessor.HttpContext?.User?.Claims?.FirstOrDefault(c => c.Type == System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
}

By using the HttpContextAccessor when the property is accessed you make sure to get the currently valid one (if there is any).

Sign up to request clarification or add additional context in comments.

6 Comments

It should not differ if the HttpContextIdentityService service is registered as Scoped and it should be.
@user1859022 - I got this error: The type or namespace name 'IUserIdentityService' could not be found
@Ayobamilaye I modified my exmpale so you can paste it in...
@Eldar removed the binding part from the explanation to clarify that accessing the HttpContext in the constructur might be to early.
@user1859022 - I followed you code, but it's still null
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.