Consider the class below:
using Microsoft.AspNet.SignalR;
public class TwitterStream
{
// Hub Context
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<GeoFeedHub>();
public void ChangeStreamBounds(double latitude, double longitude)
{
Debug.WriteLine(latitude + "-" + longitude);
}
// Lots of other interesting code redacted
}
Is it possible to call the ChangeStreamBounds
method from the client even though it is outside of the Hub class? It's possible to call client functions from the server (and from outside of the Hub class) but is it possible to do it the other way round?
Unfortunately I've backed myself into a bit of a corner and the code must be executed from the class that I've written (not from the Hub itself - unless of course you can run a SignalR Hub as a Task factory)
There may be an answer to your question that involves HubConnection
s and IHubProxy
s (which let you bind to hub method calls) or the lower-level API, but I think you may be going about this the wrong way.
Conceptually, you want the GeoFeedHub
to handle client requests, and the TwitterStream
class to handle interacting with the Twitter API. Thus, your GeoFeedHub
class has a dependency on TwitterStream
.
It's good that your TwitterStream
class has async
methods, and this is fully supported in SignalR. You can have async Hub methods that call into TwitterStream
, which removes the need for your TaskFactory
usage in Global.asax
.
Instead of creating your TwitterStream
at application start and trying to find a way to bind Hub calls to it (a backwards dependency and violation of the Single Responsibility Principle), it would be cleaner to let your Hub stand as the point of contact between your realtime clients, and inject an instance of TwitterStream
into the GeoFeedHub
so the Hub can access the Twitter API.
Here's some sample code that should illustrate this idea:
public class GeoFeedHub : Hub
{
// Declare dependency on TwitterStream class
private readonly TwitterStream _twitterStream;
// Use constructor injection to get an instance of TwitterStream
public GeoFeedHub(TwitterStream _twitterStream)
{
_twitterStream = _twitterStream;
}
// Clients can call this method, which uses the instance of TwitterStream
public async Task SetStreamBounds(double latitude, double longitude)
{
await _twitterStream.SetStreamBoundsAsync(latitude, longitude);
}
}
public class TwitterStream
{
public TwitterStream()
{
}
public async Task SetStreamBoundsAsync(double latitude, double longitude)
{
// Do something with Twitter here maybe?
await SomeComponent.SetStreamBoundsAsync(latitude, longitude);
}
// More awesome code here
}
I used DI in the example, so here's some of the glue code you'd need to hook that up. This code would go into your App_Start
folder:
// Configure Unity as our DI container
public class UnityConfig
{
private static readonly Lazy<IUnityContainer> Container = new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
RegisterTypes(container);
return container;
});
public static IUnityContainer GetConfiguredContainer()
{
return Container.Value;
}
private static void RegisterTypes(IUnityContainer container)
{
var twitterService = new TwitterService();
container.RegisterInstance(twitterService);
/*
* Using RegisterInstance effectively makes a Singleton instance of
* the object accessible throughout the application. If you don't need
* (or want) the class to be shared among all clients, then use
* container.RegisterType<TwitterService, TwitterService>();
* which will create a new instance for each client (i.e. each time a Hub
* is created, you'll get a brand new TwitterService object)
*/
}
}
// If you're using ASP.NET, this can be used to set the DependencyResolver for SignalR
// so it uses your configured container
[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(UnitySignalRActivator), "Start")]
[assembly: WebActivatorEx.ApplicationShutdownMethod(typeof(UnitySignalRActivator), "Shutdown")]
public static class UnitySignalRActivator
{
public static void Start()
{
var container = UnityConfig.GetConfiguredContainer();
GlobalHost.DependencyResolver = new SignalRUnityDependencyResolver(container);
}
public static void Shutdown()
{
var container = UnityConfig.GetConfiguredContainer();
container.Dispose();
}
}
public class SignalRUnityDependencyResolver : DefaultDependencyResolver
{
private readonly IUnityContainer _container;
public SignalRUnityDependencyResolver(IUnityContainer container)
{
_container = container;
}
public override object GetService(Type serviceType)
{
return _container.IsRegistered(serviceType)
? _container.Resolve(serviceType)
: base.GetService(serviceType);
}
public override IEnumerable<object> GetServices(Type serviceType)
{
return _container.IsRegistered(serviceType)
? _container.ResolveAll(serviceType)
: base.GetServices(serviceType);
}
}