I use ASP.NET Core 2.1 and would like to fetch User at a service level.
I've seen examples when HttpContextAccessor gets injected into some service and then we fetch the current User via UserManager
var user = await _userManager.GetUserAsync(accessor.HttpContext.User);
or in controller
var user = await _userManager.GetUserAsync(User);
Problems:
- Injecting - HttpContextAccessorinto service seems to be wrong - simply because we violate SRP and the Service Layer isn't isolated (it is dependant on http context).
- We can of course fetch user in a controller (a somewhat better approach), but we face a dilemma - we simply don't want to pass - Useras parameter in every single service method
I spent a few hours thinking about how best to implement it and have come up with a solution. I'm just not entirely sure my approach is adequate and doesn't violate any of the software-design principles.
Sharing my code in hopes to get recommendations from StackOverflow community.
The idea is the following:
First, I introduce SessionProvider which is registered as Singleton.
services.AddSingleton<SessionProvider>();
SessionProvider has a Session property which holds User, Tenant, etc.
Secondly, I introduce SessionMiddleware and register it
app.UseMiddleware<SessionMiddleware>();
In the Invoke method I resolve HttpContext, SessionProvider & UserManager.
- I fetch - User
- Then I initialise - Sessionproperty of- ServiceProvidersingleton:
sessionProvider.Initialise(user);
At this stage ServiceProvider has Session object containing the info we need.
Now we inject SessionProvider into any service and its Session object is ready for use.
Code:
SessionProvider:
public class SessionProvider
{
    public Session Session;
    public SessionProvider()
    {
        Session = new Session();
    }
    public void Initialise(ApplicationUser user)
    {
        Session.User = user;
        Session.UserId = user.Id;
        Session.Tenant = user.Tenant;
        Session.TenantId = user.TenantId;
        Session.Subdomain = user.Tenant.HostName;
    }
}
Session:
public class Session
{
    public ApplicationUser User { get; set; }
    public Tenant Tenant { get; set; }
    public long? UserId { get; set; }
    public int? TenantId { get; set; }
    public string Subdomain { get; set; }
}
SessionMiddleware:
public class SessionMiddleware
{
    private readonly RequestDelegate next;
    public SessionMiddleware(RequestDelegate next)
    {
        this.next = next ?? throw new ArgumentNullException(nameof(next));
    }
    public async Task Invoke(
        HttpContext context,
        SessionProvider sessionProvider,
        MultiTenancyUserManager<ApplicationUser> userManager
        )
    {
        await next(context);
        var user = await userManager.GetUserAsync(context.User);
        if (user != null)
        {
            sessionProvider.Initialise(user);
        }
    }
}
And now Service Layer code:
public class BaseService
{
    public readonly AppDbContext Context;
    public Session Session;
    public BaseService(
        AppDbContext context,
        SessionProvider sessionProvider
        )
    {
        Context = context;
        Session = sessionProvider.Session;
    }
}
So this is the base class for any service, as you can see we can now fetch Session object easily and it's ready for use:
public class VocabularyService : BaseService, IVocabularyService
{
    private readonly IVocabularyHighPerformanceService _vocabularyHighPerformanceService;
    private readonly IMapper _mapper;
    public VocabularyService(
        AppDbContext context,
        IVocabularyHighPerformanceService vocabularyHighPerformanceService,
        SessionProvider sessionProvider,
        IMapper mapper
        ) : base(
              context,
              sessionProvider
              )
    {
        _vocabularyHighPerformanceService = vocabularyHighPerformanceService;
        _mapper = mapper; 
    }
    public async Task<List<VocabularyDto>> GetAll()
    {
        List<VocabularyDto> dtos = _vocabularyHighPerformanceService.GetAll(Session.TenantId.Value);
        dtos = dtos.OrderBy(x => x.Name).ToList();
        return await Task.FromResult(dtos);
    }
}
Focus on the following bit:
.GetAll(Session.TenantId.Value);
also, we can easily get current user
Session.UserId.Value
or
Session.User
So, that's it.
I tested my code and it works well when several tabs are open - each tab has different subdomain in url (Tenant is resolved from subdomain - the data is being fetched correctly).
 
     
     
    