I have a working web server written in C# using SignalR. It's a self-hosted Owin Application. Everything works fine.
Now I have to relocate my controllers in different AppDomains. This breaks the SignalR part, because GlobalHost
remains the same only within one AppDomain, and is not serializable (thus I can't pass it along to other AppDomains as it is).
I've found a lot of examples/questions/tutorials about calling SignalR hubs methods from a Controller/an other class/whatever, but nothing from outside the Default AppDomain (the one where the Owin application is initialized).
How can I send a message to a client from a controller set in a different AppDomain than the Hub ?
The solution I found is pretty simple: as for any inter-AppDomain communication, we need something that can cross the boundaries of an AppDomain, thus data or a proxy to a class.
Hence, the following works:
Create a class extending MarshalByRefObject: this will automatically create a proxy to this class when we pass it to a other class in a different AppDomain
public class InterAppDomainForSignalR : MarshalByRefObject
{
public void Publish(PublishParameter param) {
var clients = GlobalHost.ConnectionManager.GetHubContext<TradeHub>().Clients;
dynamic chan;
if (param.group != null && param.group.Length > 0)
{
chan = clients.Group(param.group, param.ids);
}
else
{
if(param.ids == null || param.ids.length = 0) {
return; //not supposed to happen
}
chan = clients.Client(param.ids[0]);
}
chan.OnEvent(param.channelEvent.ChannelName, param.channelEvent);
}
}
[Serializable]
public class PublishParameter
{
public string group { get; set; }
public string[] ids { get; set; }
public ChannelEvent channelEvent { get; set; }
}
Make sure your parameters are Serializable
: here, PublishParameter
is obviously correct, BUT ChannelEvent
has to be serializable too, and contain only Serializable
members, etc...
Create an instance of this class and pass it to the objects in different AppDomains (communicationChannel
is an instance of InterAppDomainForSignalR
):
AppDomain domain = AppDomain.CreateDomain(myDomainName);
Type type = typeof(ClassInOtherAppDomain);
ClassInOtherAppDomain startpoint = (ClassInOtherAppDomain)domain.CreateInstanceAndUnwrap(
type.Assembly.FullName,
type.FullName) as ClassInOtherAppDomain;
var session = startpoint.initialize(communicationChannel);
Store the communicationChannel
in the ClassInOtherAppDomain
instance, and use it at will ;) :
public class ClassInOtherAppDomain {
private InterAppDomainForSignalR communicationChannel { get; set; }
public void initialize(InterAppDomainForSignalR communicationChannel) {
this.communicationChannel = communicationChannel;
}
public void Publish(PublishParameter param) {
this.communicationChannel.Publish(param);
}
}
That's it =)
More documentation on how to implement inter-AppDomain communication can be found here and here.
From outside:
var context = GlobalHost.ConnectionManager.GetHubContext<YOURHUBCLASS>();
context.Clients.All.yourHubTask();