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
HttpContextAccessor
into 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
User
as 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
Session
property ofServiceProvider
singleton:
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).
codereview
. Thanks! – MilliIHttpContextAccessor
to get the current user. Again that is just my opinion on the topic of this question.. – ZaxisServiceProvider
as scoped, and theSession
has all its fields being null. – MilliHttpContextAccessor
into a higher-level dependency like repo or service sounds wrong. – Milli