Como obter usuário na camada de serviço
Eu uso o ASP.NET Core 2.1 e gostaria de buscarUser
em umNível de serviço.
Eu vi exemplos quandoHttpContextAccessor
é injetado em algum serviço e, em seguida, buscamos a correnteUser
através daUserManager
var user = await _userManager.GetUserAsync(accessor.HttpContext.User);
ou no controlador
var user = await _userManager.GetUserAsync(User);
Problemas:
InjetandoHttpContextAccessor
em serviço parece sererrado - simplesmente porque violamos o SRP e oCamada de serviço não está isolado (depende decontexto http)
Podemos, é clarobuscar usuário em um controlador (uma abordagem um pouco melhor), mas enfrentamos um dilema - simplesmente não queremos passarUser
como parâmetro em todos os métodos de serviço
Passei algumas horas pensando na melhor forma de implementá-lo e encontrei uma solução. Só não tenho certeza de que minha abordagem seja adequada e não viole nenhum dos princípios de design de software.
Compartilhando meu código na esperança de obter recomendações da comunidade StackOverflow.
A ideia é a seguinte:
Primeiro, eu apresentoSessionProvider
que é registrado como Singleton.
services.AddSingleton<SessionProvider>();
SessionProvider
tem umSession
propriedade que detémUser
, Tenant
, etc.
Em segundo lugar, eu apresentoSessionMiddleware
e registre-o
app.UseMiddleware<SessionMiddleware>();
NoInvoke
método que eu resolvoHttpContext
, SessionProvider
& UserManager
.
Eu buscoUser
Então eu inicializoSession
propriedade deServiceProvider
singleton:
sessionProvider.Initialise(user);
Nesta faseServiceProvider
temSession
objeto contendo as informações que precisamos.
Agora injetamosSessionProvider
em qualquer serviço e suaSession
O objeto está pronto para uso.
Código:
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);
}
}
}
E agoraCamada de serviço código:
public class BaseService
{
public readonly AppDbContext Context;
public Session Session;
public BaseService(
AppDbContext context,
SessionProvider sessionProvider
)
{
Context = context;
Session = sessionProvider.Session;
}
}
Então este é obase classe para qualquer serviço, como você pode ver, agora podemos buscarSession
objeto facilmente e está pronto para uso:
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);
}
}
Concentre-se no seguinte bit:
.GetAll(Session.TenantId.Value);
Além disso, podemos facilmente obter usuários atuais
Session.UserId.Value
ou
Session.User
Então é isso.
Testei meu código e ele funciona bem quando várias guias estão abertas - cada guia possui um subdomínio diferente na URL (o inquilino é resolvido a partir do subdomínio - os dados estão sendo buscados corretamente).