如何在服务层获取用户

Ale*_*man 7 c# asp.net-core asp.net-core-middleware

我使用ASP.NET Core 2.1并希望User服务级别获取.

我已经看到了HttpContextAccessor注入某个服务然后我们获取当前User通道的示例UserManager

var user = await _userManager.GetUserAsync(accessor.HttpContext.User);
Run Code Online (Sandbox Code Playgroud)

或在控制器中

var user = await _userManager.GetUserAsync(User);
Run Code Online (Sandbox Code Playgroud)

问题:

  • 注入HttpContextAccessor服务似乎是错误的 - 仅仅因为我们违反了SRP并且服务层没有被隔离(它依赖于http上下文).

  • 我们当然可以在控制器中获取用户(一种更好的方法),但我们面临两难 - 我们根本不想User在每个服务方法中作为参数传递

我花了几个小时思考如何最好地实现它,并提出了一个解决方案.我不完全确定我的方法是否足够,并且不违反任何软件设计原则.

共享我的代码希望从StackOverflow社区获得建议.

这个想法如下:

首先,我介绍SessionProvider哪些注册为Singleton.

services.AddSingleton<SessionProvider>();
Run Code Online (Sandbox Code Playgroud)

SessionProvider具有Session用于保持属性User,Tenant等等.

其次,我介绍SessionMiddleware并注册它

app.UseMiddleware<SessionMiddleware>();
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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; }
}
Run Code Online (Sandbox Code Playgroud)

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在服务层代码:

public class BaseService
{
    public readonly AppDbContext Context;
    public Session Session;

    public BaseService(
        AppDbContext context,
        SessionProvider sessionProvider
        )
    {
        Context = context;
        Session = sessionProvider.Session;
    }
}
Run Code Online (Sandbox Code Playgroud)

所以这是任何服务的类,因为你可以看到我们现在可以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);
    }
}
Run Code Online (Sandbox Code Playgroud)

关注以下几点:

.GetAll(Session.TenantId.Value);
Run Code Online (Sandbox Code Playgroud)

此外,我们可以轻松获得当前用户

Session.UserId.Value
Run Code Online (Sandbox Code Playgroud)

要么

Session.User
Run Code Online (Sandbox Code Playgroud)

就是这样了.

我测试了我的代码,当打开几个选项卡时它运行良好 - 每个选项卡在url中有不同的子域(租户是从子域解析的 - 数据正在被正确获取).

Nko*_*osi 6

使用动作过滤器可以确保在动作调用管道中调用所需的行为,以便已经实现必要的依赖关系(如HttpContext.User)

ASP.NET Core中的参考过滤器

实现异步操作过滤器以避免调用.Result阻塞调用,因为它可能导致请求管道中的死锁.

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
        //...
    }
}
Run Code Online (Sandbox Code Playgroud)