Arthur S.
Arthur S.

Reputation: 482

Provide user information from signalr request in business logic layer using autofac

I have an ASP.NET MVC 5 Application with a SignalR 2 hub and using autofac for the DI.

The entire business logic is encapsulated in manager classes in their own layer. Some manager methods need informations about the current logged in user (UserId, TenantId, ..).

I solved this problem by injecting an AuthorizationProvider into each manager class that needs the user information.

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();
    }
}

Normally I can get the user information from the HttpContext and from the session. So I wrote a SessionAuthorizationProvider:

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

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

But now I have a new method in the SignalR hub that use the same mechanism.

   [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();
      }
   }

The problem is that a SignalR request doesn't have a session. Therefore I have also set the required user information in the hub method as parameters postet from the client.

So I thought it is the best solution for this problem to write a new AuthorizationProvider for SignalR and adapt the depdendency resolver. But I can't get the current user in the new SignalrAuthorizationProvider.

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

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

Is there a recommended solution to this problem?

Of course, I can extend MyMethod to accept the user information as a parameter. But MyMethod calls another method from another manager and that manager also calls another method. The user information is only needed for the last method call. So I had to change at least 3 methods and many more in the future.

Here is a sketch of the problem

Here is a sketch of the problem

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

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

Upvotes: 1

Views: 453

Answers (1)

Cyril Durand
Cyril Durand

Reputation: 16192

Session is not supported by SignalR by default and you should avoid using it. See No access to the Session information through SignalR Hub. Is my design is wrong?. But you still can use cookie or querystring to get the desired value.

In both case you need to have access to the HubCallerContext of the underlying hub, the one that is accessible through the Context property of the Hub.

In a ideal word you should just have to had the dependency to the SignalAuthorizationProvider

ie :

public class SignalrAuthorizationProvider {

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

    private readonly HubCallerContext _context; 

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

But due to SignalR design it is not possible. Context property is assigned after construction of the Hub and AFAIK there is no way to change it.

enter image description here

Source code here : HubDispatcher.cs

One possible solution would be to inject a mutable dependency inside the Hub and alter the object in the OnConnected, OnReconnected methods.

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;
    }
}

and the Hub

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();
    }
}

Having a mutable dependency is not the best design but I can't see any other way to have access to IRequest or HubCallerContext.

Instead of having an abstract Hub class which is not a perfect solution. You can change the RegisterHubs autofac method to use AOP with Castle.Core and let the interceptor calls the methods for you.

Upvotes: 2

Related Questions