Предоставление пользовательской информации из запроса сигнализатора на уровне бизнес-логики с использованием функции автозапуска - PullRequest
0 голосов
/ 08 января 2019

У меня есть приложение ASP.NET MVC 5 с концентратором SignalR 2 и использующим автофак для DI.

Вся бизнес-логика инкапсулирована в классах менеджера на их собственном уровне. Некоторые методы менеджера нуждаются в информации о текущем вошедшем в систему пользователе (UserId, TenantId, ..).

Я решил эту проблему, внедрив AuthorizationProvider в каждый класс менеджера, которому нужна информация о пользователе.

public interface IAuthorizationProvider 
{
    long? GetUserId();
    long? GteTenantId();
}

public class MyManager : IMyManager
{
    private IAuthorizationProvider _authorizationProvider;

    public MyManager(IAuthorizationProvider authorizationProvider)
    { 
        _authorizationProvider = authorizationProvider;
    }

    public void MyMethod()
    {
        // Getting the User information here is pretty simple
        long userId = _authorizationProvider.GetUserId();
    }
}

Обычно я могу получить информацию о пользователе из HttpContext и из сеанса. Поэтому я написал SessionAuthorizationProvider:

public class SessionAuthorizationProvider{
    public long? GetUserId()
    {
        HttpContext.Current?.Session?[SessionKeys.User]?.Id;
    }

    public long? GteTenantId() { ... }
}

Но теперь у меня есть новый метод в концентраторе SignalR, который использует тот же механизм.

   [HubName("myHub")]
   public class MyHub : Hub
   {
      private IMyManager _myManager;

      public MyHub(IMyManager myManager)
      { 
          _myManager = myManager;
      }

      [HubMethodName("myHubMethod")]
      public void MyHubMethod(long userId, long tenantId)
      {
          _myManager.MyMethod();
      }
   }

Проблема в том, что в запросе SignalR нет сеанса. Поэтому я также установил необходимую пользовательскую информацию в методе концентратора в качестве параметров, передаваемых от клиента.

Поэтому я подумал, что лучшим решением для этой проблемы является написание нового AuthorizationProvider для SignalR и адаптация решателя зависимостей. Но я не могу получить текущего пользователя в новом SignalrAuthorizationProvider.

public class SignalrAuthorizationProvider{
    public long? GetUserId()
    {
        // How to get the user information here???
    }

    public long? GteTenantId() { /* and here??? */ }
}

Есть ли рекомендуемое решение этой проблемы?

Конечно, я могу расширить MyMethod, чтобы принимать информацию о пользователе в качестве параметра. Но MyMethod вызывает другой метод из другого менеджера, и этот менеджер также вызывает другой метод. Информация о пользователе необходима только для последнего вызова метода. Поэтому мне пришлось изменить как минимум 3 метода и еще много в будущем.

Вот эскиз проблемы

Here is a sketch of the problem

Это потенциальное решение. Но это очень плохо

This is a potential solution. But it's very bad

1 Ответ

0 голосов
/ 11 января 2019

Сессия не поддерживается SignalR по умолчанию, и вам следует избегать ее использования. См. Нет доступа к информации о сеансе через SignalR Hub. У меня неправильный дизайн? . Но вы все равно можете использовать cookie или строку запроса, чтобы получить желаемое значение.

В обоих случаях вам необходим доступ к HubCallerContext базового концентратора, который доступен через свойство Context Hub.

В идеальном слове вам просто нужно иметь зависимость от SignalAuthorizationProvider

то есть:

public class SignalrAuthorizationProvider {

    public SignalrAuthorizationProvider(HubCallerContext context){
        this._context = context;
    }

    private readonly HubCallerContext _context; 

    public long? GetUserId() {
        return this._context.Request.QueryString["UserId"]
    }
}

Но из-за дизайна SignalR это невозможно. Context свойство назначается после постройки Хаба, и AFAIK изменить его невозможно.

enter image description here

Исходный код здесь: HubDispatcher.cs

Одним из возможных решений было бы внедрение изменяемой зависимости внутри Hub и изменение объекта в методах OnConnected, OnReconnected.

public class SignalrAuthorizationProvider : IAuthorizationProvider
{
    private Boolean _isInitialized;
    private String _userId;
    public String UserId
    {
        get
        {
            if (!_isInitialized)
            {
                throw new Exception("SignalR hack not initialized");
            }
            return this._userId;
        }
    }

    public void OnConnected(HubCallerContext context)
    {
        this.Initialize(context);
    }
    public void OnReconnected(HubCallerContext context)
    {
        this.Initialize(context); 
    }

    private void Initialize(HubCallerContext context) {
        this._userId = context.QueryString["UserId"];
        this._isInitialized = true;
    }
}

и концентратор

public abstract class CustomHub : Hub
{
    public CustomHub(IAuthorizationProvider authorizationProvider)
    {
        this._authorizationProvider = authorizationProvider;
    }

    private readonly IAuthorizationProvider _authorizationProvider;

    public override Task OnConnected()
    {
        this._authorizationProvider.OnConnected(this.Context);

        return base.OnConnected();
    }

    public override Task OnReconnected()
    {
        this._authorizationProvider.OnReconnected(this.Context);
        return base.OnReconnected();
    }
}

Наличие изменяемой зависимости - не лучший дизайн, но я не вижу другого способа получить доступ к IRequest или HubCallerContext.

Вместо абстрактного Hub класса, который не является идеальным решением. Вы можете изменить метод RegisterHubs autofac для использования AOP с Castle.Core и позволить перехватчику вызывать методы для вас.

...