本文介绍了如何在服务层获得用户的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用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属性,其中包含UserTenant等.

SessionProvider has a Session property which holds User, Tenant, etc.

其次,我介绍SessionMiddleware并进行注册

Secondly, I introduce SessionMiddleware and register it

app.UseMiddleware<SessionMiddleware>();

Invoke方法中,我解析HttpContextSessionProvider& 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)

参考过滤器位于ASP.NET Core

实施异步操作过滤器,以避免调用.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
        //...
    }
}

这篇关于如何在服务层获得用户的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-24 10:18