Cómo obtener un Usuario en la Capa de Servicio
Uso ASP.NET Core 2.1 y me gustaría obtenerUser
en unnivel de servici.
He visto ejemplos cuandoHttpContextAccessor
se inyecta en algún servicio y luego buscamos el @ actuUser
viaUserManager
var user = await _userManager.GetUserAsync(accessor.HttpContext.User);
o en el controlador
var user = await _userManager.GetUserAsync(User);
Problemas
InyecciónHttpContextAccessor
en servicio parece serincorrect - simplemente porque violamos SRP y laService Layer no está aislado (depende dehttp context).
Podemos, por supuesto,fetch usuario en un controlador (un enfoque algo mejor), pero nos enfrentamos a un dilema: simplemente no queremos pasarUser
como parámetro en cada método de servicio individual
Pasé unas horas pensando en la mejor manera de implementarlo y se me ocurrió una solución. Simplemente no estoy completamente seguro de que mi enfoque sea adecuado y no viole ninguno de los principios de diseño de software.
Compartir mi código con la esperanza de obtener recomendaciones de la comunidad StackOverflow.
La idea es la siguiente
Primero, presentoSessionProvider
que está registrado como Singleton.
services.AddSingleton<SessionProvider>();
SessionProvider
tiene unSession
propiedad que contieneUser
, Tenant
, etc.
n segundo lugar, presento aSessionMiddleware
y regístralo
app.UseMiddleware<SessionMiddleware>();
En elInvoke
método resuelvoHttpContext
, SessionProvider
& UserManager
.
I fetchUser
Entonces inicializoSession
propiedad deServiceProvider
singleton:
sessionProvider.Initialise(user);
En este puntoServiceProvider
tieneSession
objeto que contiene la información que necesitamos.
Ahora inyectamosSessionProvider
en cualquier servicio y suSession
objeto está listo para usar.
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);
}
}
}
Y ahoraService Layer código:
public class BaseService
{
public readonly AppDbContext Context;
public Session Session;
public BaseService(
AppDbContext context,
SessionProvider sessionProvider
)
{
Context = context;
Session = sessionProvider.Session;
}
}
Así que esta es labas class para cualquier servicio, como puede ver ahora podemos obtenerSession
objeto fácilmente y está listo para usar:
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);
}
}
Céntrate en el siguiente bit:
.GetAll(Session.TenantId.Value);
también, podemos obtener fácilmente el usuario actual
Session.UserId.Value
Session.User
Eso es todo
Probé mi código y funciona bien cuando hay varias pestañas abiertas: cada pestaña tiene un subdominio diferente en la URL (el inquilino se resuelve desde el subdominio; los datos se obtienen correctamente).