Я использую ASP.NET Core 2.1 и хочу получить User
на уровне обслуживания.
Я видел примеры, когда HttpContextAccessor
внедряется в какой-то сервис, а затем мы получаем текущий User
через UserManager
.
var user = await _userManager.GetUserAsync(accessor.HttpContext.User);
или в контроллере
var user = await _userManager.GetUserAsync(User);
Проблемы:
Внедрение
HttpContextAccessor
в сервис кажется неправильным – просто потому, что мы нарушаем SRP, а уровень сервиса не изолирован (он зависит от контекста http). ).Конечно, мы можем извлекать пользователя в контроллере (это несколько лучший подход), но мы сталкиваемся с дилеммой — мы просто не хотим передавать
User
в качестве параметра в каждом отдельном сервисном методе.
Я потратил несколько часов на размышления о том, как лучше всего это реализовать, и нашел решение. Я просто не совсем уверен, что мой подход адекватен и не нарушает ни один из принципов проектирования программного обеспечения.
Делюсь своим кодом в надежде получить рекомендации от сообщества StackOverflow.
Идея заключается в следующем:
Во-первых, я представляю SessionProvider
, который зарегистрирован как Singleton.
services.AddSingleton<SessionProvider>();
SessionProvider
имеет свойство Session
, которое содержит User
, Tenant
и т. д.
Во-вторых, я ввожу SessionMiddleware
и регистрирую его
app.UseMiddleware<SessionMiddleware>();
В методе Invoke
я разрешаю HttpContext
, SessionProvider
и UserManager
.
Я приношу
User
Затем я инициализирую свойство
Session
синглтонаServiceProvider
:
sessionProvider.Initialise(user);
На данном этапе ServiceProvider
имеет Session
объект, содержащий нужную нам информацию.
Теперь мы внедряем SessionProvider
в любой сервис, и его объект Session
готов к использованию.
Код:
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);
}
}
}
А теперь код Service Layer:
public class BaseService
{
public readonly AppDbContext Context;
public Session Session;
public BaseService(
AppDbContext context,
SessionProvider sessionProvider
)
{
Context = context;
Session = sessionProvider.Session;
}
}
Итак, это базовый класс для любого сервиса, как видите, теперь мы можем легко получить объект Session
, и он готов к использованию:
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);
}
}
Сосредоточьтесь на следующем фрагменте:
.GetAll(Session.TenantId.Value);
также мы можем легко получить текущего пользователя
Session.UserId.Value
or
Session.User
Ну это все.
Я протестировал свой код, и он хорошо работает, когда открыто несколько вкладок — каждая вкладка имеет свой поддомен в URL-адресе (клиент разрешается из поддомена — данные извлекаются правильно).
codereview
. Спасибо! - person Alex Herman   schedule 04.08.2018IHttpContextAccessor
для получения текущего пользователя. Опять же, это только мое мнение по теме этого вопроса. - person Nkosi   schedule 04.08.2018ServiceProvider
как ограниченный, аSession
все поля равны нулю. - person Alex Herman   schedule 04.08.2018HttpContextAccessor
в зависимость более высокого уровня, такую как репо или служба, звучит неправильно. - person Alex Herman   schedule 21.10.2018