SignalR target a specific user from server with custom IUserIdProvider - c#

I've an application using SignalR & WebAPI. I've a custom Authorization context based on a token, which I give on each SignalR requests using the QueryString.
I've implement and IUserIdProvider, in order to retrieve my User from the Token.
And finally, I want to call a client method from the server, for a specific User (with is ID), I'm using a HubContext from the GlobalHost.ConnectionManager.
My problem is that my User is never find from my HubContext, but it is from the Hub itself...
Here is my IUserIdProvider implementation
public class SignalRUserIdProvider : IUserIdProvider
{
public string GetUserId(IRequest request)
{
var token = request.QueryString["token"];
var scope = GetUnprotectedScope(token);
if (scope == null)
return null;
return scope.Id_User.ToString();
}
}
Here is my Hub imlementation
[HubName("notifier")]
public class NotifierHub : Hub
{
public void Notify(string message)
{
Clients.User("1").Notify(message); //When call from a client, this works very well, only the User with the Id = 1 receive the notification
}
}
And finally, I use this to call the client method from my server:
GlobalHost
.ConnectionManager
.GetHubContext<NotifierHub>()
.Clients
.User("1")
.Notify(notification.Message);
// This does nothing...
I'm out of solution at this point, I don't understand what happens, does anyone have already achieve this ?

I've finally figured the issue, but I don't know how to fix it...
The actual issue is simple, the Hub itself has a good context with the clients and everything, but the GlobalHost.ConnectionManager have nothing.
If I change my Hub to something like this:
[HubName("notifier")]
public class NotifierHub : Hub
{
public void Notify(string message)
{
Clients.User("1").Notify(message + " from Hub itself");
GlobalHost
.ConnectionManager
.GetHubContext<NotifierHub>()
.Clients
.User("1")
.Notify(message + " from ConnectionManager");
}
}
My client receive "My message from Hub itself", but never receive "My message from ConnectionManager".
In conclusion, I've a problem with my DependencyInjection... I'm using Structuremap, with this DependencyResover :
public class StructureMapSignalRDependencyResolver : DefaultDependencyResolver
{
private IContainer _container;
public StructureMapSignalRDependencyResolver(IContainer container)
{
_container = container;
}
public override object GetService(Type serviceType)
{
if (serviceType == null)
return null;
var service = _container.TryGetInstance(serviceType) ?? base.GetService(serviceType);
if (service != null) return service;
return (!serviceType.IsAbstract && !serviceType.IsInterface && serviceType.IsClass)
? _container.GetInstance(serviceType)
: _container.TryGetInstance(serviceType);
}
public override IEnumerable<object> GetServices(Type serviceType)
{
var objects = _container.GetAllInstances(serviceType).Cast<object>();
return objects.Concat(base.GetServices(serviceType));
}
}
My Startup file:
public void Configuration(IAppBuilder app)
{
app.Map("/signalr", RegisterSignalR);
}
public static void RegisterSignalR(IAppBuilder map)
{
var resolver = new StructureMapSignalRDependencyResolver(IoC.Initialize());
var config = new HubConfiguration { Resolver = resolver };
map.UseCors(CorsOptions.AllowAll);
map.RunSignalR(config);
}
And finally my Registry...
For<Microsoft.AspNet.SignalR.IDependencyResolver>().Add<StructureMapSignalRDependencyResolver>();
For<INotifier>().Use<SignalRNotifier>();

Related

Glabal variable not dependant on session

Is there a way to have a global variable that is not session dependent?
I want to have a list of all the SignalR connections and the user ID form the db.
So far I have something like this :
public class SignalRUsersService : ISignalRUsersService
{
private int userId;
private readonly IHttpContextAccessor _accessor;
public List<ConnectedSignalR> SignalRUsers;
public SignalRUsersService( IHttpContextAccessor accessor)
{
_accessor = accessor;
try
{
userId = Convert.ToInt32(_accessor.HttpContext.Session.GetString("uID"));
}
catch
{
userId = 0;
}
SignalRUsers = new List<ConnectedSignalR>();
}
public void AddSignalRUser(string ConnID)
{
SignalRUsers.Add(new ConnectedSignalR()
{
ConnID = ConnID,
UserID = userId
});
}
public void RemoveSignalRUser(string ConnID)
{
var usr = SignalRUsers.Where(a => a.ConnID == ConnID).FirstOrDefault();
if (usr != null)
{
SignalRUsers.Remove(usr);
}
}
public List<ConnectedSignalR> GetSignalRUsers()
{
return SignalRUsers;
}
}
The problem is every time I use the interface it seems it's re-initializing my SignalRUsers list. And even if I push the data in the list I find it null when I need it.
Is SignalRUsers session dependent? Or is it just a matter of not using the interface in a right way?
Any help is much appreciated.
SignalR hubs are transient, which means a new instance is created when a method call is received from a client (see: https://learn.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/hubs-api-guide-server).
You are probably injecting this service into a hub class, which means you can use dependency injection configuration to control the lifetime of your service class.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ISignalRUsersService, SignalRUsersService>();
}

Setup SignalR and Service Bus inside a Azure Service Fabric service

I am porting an existing Cloud Service WorkerRole to Service Fabric as a stateless service. The original Cloud Service uses SignalR and Service Bus (as a SignalR backplane), to send notifications out to any client listening. There is a Startup class that does some of the setup:
class Startup
{
public void Configuration(IAppBuilder app)
{
String connectionString = "Endpoint=sb://[name].servicebus.windows.net/;SharedSecretIssuer=owner;SharedSecretValue=[key]";
GlobalHost.DependencyResolver.UseServiceBus(connectionString, "InSys");
app.MapSignalR();
Notifications.Hub = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
}
}
In the OnStart() method in for the WorkerRole I kick-off OWIN with:
var endpoint = RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["HttpEndpoint"];
var baseUri = $"{endpoint.Protocol}://{endpoint.IPEndpoint}";
var app = WebApp.Start<Startup>(new StartOptions(url: baseUri));
How is this (i.e., connection the to SignalR Service Bus Backplane) done for a stateless service within Service Fabric?
With the help of https://github.com/marcinbudny/SignalRSelfHostScaleOut (which is an example of scaleout using Redis) I think I have this licked.
In the ServiceManifest.xml I added the following EndPoint:
<Endpoint Protocol="http" Name="ServiceEndpoint" Type="Input" Port="8322" />
I also added a Startup class:
public static class Startup
{
public static void ConfigureApp(IAppBuilder app)
{
String connectionString = "Endpoint=sb://[name].servicebus.windows.net/;SharedSecretIssuer=owner;SharedSecretValue=[value]";
GlobalHost.DependencyResolver.UseServiceBus(connectionString, "InSys");
app.MapSignalR();
Notifications.Hub = GlobalHost.ConnectionManager.GetHubContext<InSysMainHub>();
}
}
An OwinCommunicationListener class was also added:
public class OwinCommunicationListener : ICommunicationListener
{
private readonly ServiceEventSource eventSource;
private readonly Action<IAppBuilder> startup;
private readonly ServiceContext serviceContext;
private readonly string endpointName;
private readonly string appRoot;
private IDisposable webApp;
private string publishAddress;
private string listeningAddress;
public OwinCommunicationListener(Action<IAppBuilder> startup, ServiceContext serviceContext, ServiceEventSource eventSource, string endpointName)
: this(startup, serviceContext, eventSource, endpointName, null)
{
}
public OwinCommunicationListener(Action<IAppBuilder> startup, ServiceContext serviceContext, ServiceEventSource eventSource, string endpointName, string appRoot)
{
if (startup == null)
{
throw new ArgumentNullException(nameof(startup));
}
if (serviceContext == null)
{
throw new ArgumentNullException(nameof(serviceContext));
}
if (endpointName == null)
{
throw new ArgumentNullException(nameof(endpointName));
}
if (eventSource == null)
{
throw new ArgumentNullException(nameof(eventSource));
}
this.startup = startup;
this.serviceContext = serviceContext;
this.endpointName = endpointName;
this.eventSource = eventSource;
this.appRoot = appRoot;
}
public Task<string> OpenAsync(CancellationToken cancellationToken)
{
var serviceEndpoint = this.serviceContext.CodePackageActivationContext.GetEndpoint(this.endpointName);
var protocol = serviceEndpoint.Protocol;
int port = serviceEndpoint.Port;
if (this.serviceContext is StatefulServiceContext)
{
StatefulServiceContext statefulServiceContext = (StatefulServiceContext) serviceContext;
listeningAddress = string.Format(
CultureInfo.InvariantCulture,
"{0}://+:{1}/{2}{3}/{4}/{5}",
protocol,
port,
string.IsNullOrWhiteSpace(appRoot)
? string.Empty
: appRoot.TrimEnd('/') + '/',
statefulServiceContext.PartitionId,
statefulServiceContext.ReplicaId,
Guid.NewGuid());
}
else if (serviceContext is StatelessServiceContext)
{
listeningAddress = string.Format(
CultureInfo.InvariantCulture,
"{0}://+:{1}/{2}",
protocol,
port,
string.IsNullOrWhiteSpace(appRoot)
? string.Empty
: appRoot.TrimEnd('/') + '/');
}
else
{
throw new InvalidOperationException();
}
publishAddress = listeningAddress.Replace("+", FabricRuntime.GetNodeContext().IPAddressOrFQDN);
try
{
eventSource.Message("Starting web server on " + listeningAddress);
webApp = WebApp.Start(listeningAddress, appBuilder => startup.Invoke(appBuilder));
eventSource.Message("Listening on " + this.publishAddress);
return Task.FromResult(this.publishAddress);
}
catch (Exception ex)
{
eventSource.Message("Web server failed to open endpoint {0}. {1}", this.endpointName, ex.ToString());
StopWebServer();
throw;
}
}
public Task CloseAsync(CancellationToken cancellationToken)
{
this.eventSource.Message("Closing web server on endpoint {0}", this.endpointName);
this.StopWebServer();
return Task.FromResult(true);
}
public void Abort()
{
this.eventSource.Message("Aborting web server on endpoint {0}", this.endpointName);
this.StopWebServer();
}
private void StopWebServer()
{
if (this.webApp != null)
{
try
{
this.webApp.Dispose();
}
catch (ObjectDisposedException)
{
// no-op
}
}
}
}
And then finally I changed the CreateServiceInstanceListeners method in my stateless service code to:
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
return new[]
{
new ServiceInstanceListener(serviceContext => new OwinCommunicationListener(Startup.ConfigureApp, serviceContext, ServiceEventSource.Current, "ServiceEndpoint"))
};
}
Create a Stateless service With Owin listener. Then in start up configure for signalR and backplane(service bus or sql). The issue ideally you would face is with a negotiate(Hand shake between Signalr client with server) At this point try configure for cross origin request, a sample code for persistent connection will look like below.
Also note the line appBuilder.UseAesDataProtectorProvider("Your Key") as it is important. The result of this is that you wont end up getting HTTP 400 to connect most of the time. This is because SignalR will make at least 2 requests at handshake and those will usually hit two different machines.
Thanks to marcin budny on the explanation.
var config = new HttpConfiguration();
// Configure your origins as required.
var cors = new EnableCorsAttribute("*", "*", "*");
config.EnableCors(cors);
FormatterConfig.ConfigureFormatters(config.Formatters);
RouteConfig.RegisterRoutes(config.Routes);
appBuilder.UseWebApi(config);
GlobalHost.DependencyResolver.UseServiceBus("yourconnection string comes here", "signalrbackplaneserver");
appBuilder.UseAesDataProtectorProvider("some password");
appBuilder.Map("/echo", map =>
{
map.UseCors(CorsOptions.AllowAll).RunSignalR<MyEndPoint>();
});

SignalR & SimpleInjector silently failing between hub and frontend

I'm trying to set up a signalr hub on my project, using simpleinjector for dependency injection.
I have some configuration in Startup.cs
[assembly: OwinStartup(typeof(Startup))]
namespace CallCentre.Client
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
var container = new Container();
container.Register<TwilioHub>();
container.Verify();
var config = new HubConfiguration()
{
Resolver = new SignalRSimpleInjectorDependencyResolver(container)
};
app.MapSignalR(config);
}
}
}
DI Resolver
public class SignalRSimpleInjectorDependencyResolver : DefaultDependencyResolver
{
private readonly Container _container;
public SignalRSimpleInjectorDependencyResolver(Container container)
{
_container = container;
}
public override object GetService(Type serviceType)
{
return ((IServiceProvider)_container).GetService(serviceType)
?? base.GetService(serviceType);
}
public override IEnumerable<object> GetServices(Type serviceType)
{
return _container.GetAllInstances(serviceType)
.Concat(base.GetServices(serviceType));
}
}
My hub
public class TwilioHub : Hub
{
public void Send(int callCount, int queueId)
{
var context = GlobalHost.ConnectionManager.GetHubContext<TwilioHub>();
context.Clients.All.updateQueueCount(callCount, queueId);
}
}
A class elsewhere in the solution where the hub is called
public class QueueStateHandler : IQueueStateHandler
{
private readonly TwilioHub _twilioHub;
public QueueStateHandler(TwilioHub twilioHub)
{
_twilioHub = twilioHub;
}
public void IncrementQueueById(int id)
{
_twilioHub.Send(5,1);
}
}
And my frontend code
$(function () {
var hub = $.connection.twilioHub;
hub.logging = true;
var queue = $('#QueueCount');
hub.client.updateQueueCount = function(queueCount, id) {
alert(queueCount);
};
$.connection.hub.start();
});
I can set everything up using straight web api, skipping the DI and everything works fine. As soon as I bring in DI I start running into problems. At the moment its silently falling over somewhere. I can step through the code and end up in the hub, but nothing happens in my frontend.
Any pointers as to what I'm doing wrong would be greatly appreciated.

No clients available in SignalR Hub context

I am new to SignalR and am experimenting with setting up a self host service that I can use to send events to other applications. I am having a problem getting a method to be called on all clients. In the code below _context.Clients.All is always empty. Any Ideas?
Test Method:
var server = new SignalRServer();
server.Start("http://localhost:8555");
var hubConnection = new HubConnection("http://localhost:8555");
var proxy = hubConnection.CreateHubProxy("testHub");
var executed = false;
proxy.On("testMethod", () =>
{
executed = true;
});
hubConnection.Start().Wait();
var hub = new TestHubContext(GlobalHost.ConnectionManager.GetHubContext<TestHub>());
hub.Test();
hubConnection.Stop();
server.Stop();
Self host server:
public class SignalRServer
{
private IDisposable _signalR;
public void Start(string url)
{
_signalR = WebApp.Start<SignalRStartup>(url);
}
public void Stop()
{
_signalR.Dispose();
}
}
public class SignalRStartup
{
public static IAppBuilder App = null;
public void Configuration(IAppBuilder app)
{
app.UseCors(CorsOptions.AllowAll);
app.MapSignalR();
}
}
Hub:
public class TestHub : Hub
{
}
public class TestHubContext
{
private IHubContext _context;
public TestHubContext(IHubContext context)
{
_context = context;
}
public void Test()
{
if (_context != null)
{
// At this poing _context.Clients.All is always empty
_context.Clients.All.testMethod();
}
}
}
I think your context / client connections are fine. Without further information I'm guessing your problem is that you are closing your connection and server too quickly after calling hub.Test()
Comment out the two .Stop() calls (and stick a Console.ReadLine in there to keep the console open) and see what happens

SignalR cannot call client function when using StructureMap dependency resolver

When I include a structuremap dependency resolver in my global.asax for signalR any calls to client functions no longer arrive at the browser although I can see them if in the logging pipeline.
Here's an extract of my global.asax:
var container = ObjectFactory.Container;
// Now configure SignalR, MVC, ASP.Net and SharpRepository
GlobalHost.HubPipeline.AddModule(new ErrorHandlingPipelineModule());
GlobalHost.HubPipeline.AddModule(new LoggingPipelineModule());
// TODO: Work out why this breaks the connection between the server and the client.
GlobalHost.DependencyResolver =
ObjectFactory.GetInstance<IDependencyResolver>();
RouteTable.Routes.MapHubs();
DependencyResolver.SetResolver(
new StructureMapDependencyResolver(container));
GlobalConfiguration.Configuration.DependencyResolver =
new StructureMapDependencyResolver(container);
RepositoryDependencyResolver.SetDependencyResolver(
new SharpRepository.Ioc.StructureMap.StructureMapDependencyResolver(container));
Here the implementation of the structuremap resolver:
public class StructureMapSignalRDependencyResolver
: DefaultDependencyResolver
{
private readonly IContainer _container;
public StructureMapSignalRDependencyResolver(
IContainer container)
{
if (container == null)
{
throw new ArgumentNullException("container");
}
_container = container;
}
public override object GetService(Type serviceType)
{
object result = null;
try
{
result =
!serviceType.IsAbstract &&
!serviceType.IsInterface &&
serviceType.IsClass
? _container.GetInstance(serviceType)
: (_container.TryGetInstance(serviceType)
?? base.GetService(serviceType));
}
catch (Exception ex)
{
Tracing.Error(
"[StructureMapSignalRDependencyResolver]",
Tracing.SerializeException(ex));
}
if (result == null)
{
Tracing.Information(
"[StructureMapSignalRDependencyResolver]",
"Could retrieve object of type {0}",serviceType.ToString());
}
return result;
}
public override IEnumerable<object> GetServices(
Type serviceType)
{
IEnumerable<object> result = null;
try
{
result = _container.GetAllInstances(serviceType)
.Cast<object>().Concat(
base.GetServices(serviceType));
}
catch (Exception ex)
{
Tracing.Error(
"[StructureMapSignalRDependencyResolver]",
Tracing.SerializeException(ex));
}
if (result == null)
{
Tracing.Information(
"[StructureMapSignalRDependencyResolver]",
"Could retrieve object of type {0}", serviceType.ToString());
}
return result;
}
public override void Register(Type serviceType,
Func<object> activator)
{
Tracing.Information(
"[StructureMapSignalRDependencyResolver]",
"Registering object of type {0}",
serviceType.ToString());
base.Register(serviceType, activator);
}
public override void Register(Type serviceType,
IEnumerable<Func<object>> activators)
{
Tracing.Information(
"[StructureMapSignalRDependencyResolver]",
"Registering object of type {0}",
serviceType.ToString());
base.Register(serviceType, activators);
}
}
Here is the implementation of StructureMap Registry (there are many more which include the application assemblies).
[RegistryOrder(Order = 6)]
public class SignalRRegistry : Registry
{
public SignalRRegistry()
{
For<IDependencyResolver>().Singleton()
.Use<StructureMapSignalRDependencyResolver>();
For<IHubConnectionContext>().Singleton()
.Use(GlobalHost.ConnectionManager
.GetHubContext<BOSSHub>().Clients);
}
//public void Configure()
//{
// GlobalHost.DependencyResolver =
// ObjectFactory.GetInstance<IDependencyResolver>();
// RouteTable.Routes.MapHubs();
//}
}
I've rolled back the Hub class so that it no longer has an dependencies; the output from WhatDoIHave seems to have some entries for Microsoft.AspNet.SignalR but without any concrete relationships.
I wondering if I missed a step in my registry ? I get no exceptions anywhere; it just stops working, if I comment out the GlobalHost.DependencyResolver line from global.asax all is well.
I hope someone could share with me a DI implementation for signalR that is working for them.
Many Thanks
UPDATE: Thanks for sorting out the bad formatting for me - appreciate that.
UPDATE: I've put together a small test project which shows the issue. I was surprised if I'm honest I was able to reproduce it as the application that I'm working on it pretty big and complex with lot's of StructureMap stuff going on. I've uploaded into GitHub : https://github.com/johnk27stars/SignalRTest.git - Thanks to anyone who could spare a minute to take a look.
After some honest advice from Jeremy Miller we switched to AutoFac and it working fine.

Categories

Resources