问题描述
我使用ASP.NET Core 2.1,并希望在服务级别获取User
.
I use ASP.NET Core 2.1 and would like to fetch User
at a service level.
我已经看到了将HttpContextAccessor
注入到某些服务中然后通过UserManager
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);
或在控制器中
var user = await _userManager.GetUserAsync(User);
问题:
Problems:
-
将
HttpContextAccessor
注入服务似乎是错误-仅仅是因为我们违反了SRP,并且 Service Layer 不是孤立的(它依赖于 http上下文).
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).
我们当然可以在控制器中获取用户(一种更好的方法),但是我们面临一个难题-我们根本不想在每个控制器中都将User
作为参数传递单一服务方式
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.
共享我的代码,希望能从StackOverflow社区获得建议.
Sharing my code in hopes to get recommendations from StackOverflow community.
想法如下:
首先,我介绍注册为Singleton的SessionProvider
.
First, I introduce SessionProvider
which is registered as Singleton.
services.AddSingleton<SessionProvider>();
SessionProvider
具有Session
属性,其中包含User
,Tenant
等.
SessionProvider
has a Session
property which holds User
, Tenant
, etc.
其次,我介绍SessionMiddleware
并进行注册
Secondly, I introduce SessionMiddleware
and register it
app.UseMiddleware<SessionMiddleware>();
在Invoke
方法中,我解析HttpContext
,SessionProvider
& UserManager
.
In the Invoke
method I resolve HttpContext
, SessionProvider
& UserManager
.
-
我获取了
User
然后初始化ServiceProvider
单例的Session
属性:
Then I initialise Session
property of ServiceProvider
singleton:
sessionProvider.Initialise(user);
在此阶段,ServiceProvider
具有包含我们所需信息的Session
对象.
At this stage ServiceProvider
has Session
object containing the info we need.
现在,我们将SessionProvider
注入到任何服务中,并且其Session
对象已可以使用.
Now we inject SessionProvider
into any service and its Session
object is ready for use.
代码:
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
对象,并且可以使用了:
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);
}
}
关注以下位:
.GetAll(Session.TenantId.Value);
此外,我们可以轻松获取当前用户
also, we can easily get current user
Session.UserId.Value
或
Session.User
就是这样.
我测试了我的代码,当打开几个选项卡时,它很好用-每个选项卡在url中都有不同的子域(租户是从子域解析的-数据已正确提取).
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).
推荐答案
使用动作过滤器将确保您所需的行为在动作调用管道中的调用足够晚,以至于已经实现了必要的依赖,例如 HttpContext.User)
Using an action filter would ensure that your desired behavior is invoked late enough in the action invocation pipeline that the necessary dependencies have already been realized, (like HttpContext.User)
实施异步操作过滤器,以避免调用.Result
阻止调用,因为这可能会导致请求管道中出现死锁.
Implement an async action filter to avoid calling .Result
blocking calls as it may cause deadlocks in the request pipeline.
public class SessionFilter : IAsyncActionFilter {
public async Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next) {
// do something before the action executes
var serviceProvider = context.HttpContext.RequestServices;
var sessionProvider = serviceProvider.GetService<SessionProvider>();
var userManager = serviceProvider.GetService<MultiTenancyUserManager<ApplicationUser>>()
var user = await userManager.GetUserAsync(context.HttpContext.User);
if (user != null) {
sessionProvider.Initialise(user);
}
//execute action
var resultContext = await next();
// do something after the action executes; resultContext.Result will be set
//...
}
}
这篇关于如何在服务层获得用户的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!