【发布时间】:2019-01-12 04:45:22
【问题描述】:
我使用 ASP.NET Core 2.1 并希望在 服务级别获取 User。
我见过HttpContextAccessor 被注入某个服务然后我们通过UserManager 获取当前User 的例子
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然后我初始化
ServiceProvider单例的Session属性:
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);
}
}
}
现在服务层代码:
public class BaseService
{
public readonly AppDbContext Context;
public Session Session;
public BaseService(
AppDbContext context,
SessionProvider sessionProvider
)
{
Context = context;
Session = sessionProvider.Session;
}
}
所以这是任何服务的 base 类,如您所见,我们现在可以轻松获取 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
或
Session.User
所以,就是这样。
我测试了我的代码,当打开多个选项卡时它运行良好 - 每个选项卡在 url 中有不同的子域(租户从子域解析 - 数据被正确获取)。
【问题讨论】:
-
如果这是工作代码并且除了认为它不是一个好的设计之外确实没有实际问题,那么我会说这个问题对于 SO 来说是题外话,因为它更像是一个代码审查应该适合codereview.stackexchange.com
-
@Nkosi 啊,我明白了。下次我会在
codereview上问这种问题。谢谢! -
请注意,您过于关注抽象可以很好地工作的实现问题。抽象会话提供者简化了服务的依赖关系。因此,您使用
IHttpContextAccessor获取当前用户甚至都没有关系。再次,这只是我对这个问题的主题的看法.. -
我也刚刚注意到,在将上下文传递到管道之后,您正在中间件中设置会话,这意味着该会话对管道中的其他处理程序不可用
-
嗨@Nkosi,你是绝对正确的。我刚刚尝试将
ServiceProvider注册为作用域,而Session的所有字段都为空。
标签: c# asp.net-core asp.net-core-middleware