I'm trying to improve my application which will require calling a hub from C# instead of javascript. The current workflow for adding a task in my app is:
- make an API call to add the data to the database
- return new record to AngularJS controller
- invoke hub's method from controller
- hub broadcasts call to clients appropriately
What I would like to do is bypass calling the hub's method from my AngularJS controller and call it directly from my API controller method.
This is what my hub currently looks like:
public class TaskHub : Hub
{
public void InsertTask(TaskViewModel task)
{
Clients.Caller.onInsertTask(task, false);
Clients.Others.onInsertTask(task, true);
}
}
There are many SO threads out there on the topic, but everything I've read would have me adding the following code to my API controller:
var hubContext = GlobalHost.ConnectionManager.GetHubContext<TaskHub>();
hubContext.Clients.All.onInsertTask(task);
There are number of issues with this. First and foremost, I want the client broadcast calls to exist in a single class, not called directly from my API controller(s). Secondly, hubContext
is an instance of IHubContext
rather than IHubCallerConnectionContext
. This means I only have access to all clients and couldn't broadcast different responses to the Caller
and Others
like I'm currently doing.
Is there a way to truly call a hub's method from C# and, ideally, have access to the different caller options? Ideally, I'd be able to do something as easy as the following from my API controller (or better yet, a solution with DI):
var taskHub = new TaskHub();
taskHub.InsertTask(task);
Thanks in advance.
SOLUTION
For posterity, I thought I'd include my full solution as per Wasp's suggestion.
First, I modified my javascript (AngularJS) service to include the SignalR connection ID in a custom request header for the API call, in this case an INSERT:
var addTask = function (task) {
var config = {
headers: { 'ConnectionId': connection.id }
};
return $http.post('/api/tasks', task, config);
};
Then, I retrieved the connection ID from the request in my API controller after performing the applicable CRUD operation and then called my hub:
public HttpResponseMessage Post(HttpRequestMessage request, [FromBody]TaskViewModel task)
{
var viewModel = taskAdapter.AddTask(task);
var connectionId = request.Headers.GetValues("ConnectionId").FirstOrDefault();
TaskHub.InsertTask(viewModel, connectionId);
return request.CreateResponse(HttpStatusCode.OK, viewModel);
}
My hub looks like this where I'm now only using static methods called from my API controller:
public class TaskHub : Hub
{
private static IHubContext context = GlobalHost.ConnectionManager.GetHubContext<TaskHub>();
public static void InsertTask(TaskViewModel task, string connectionId)
{
if (!String.IsNullOrEmpty(connectionId))
{
context.Clients.Client(connectionId).onInsertTask(task, false);
context.Clients.AllExcept(connectionId).onInsertTask(task, true);
}
else
{
context.Clients.All.onInsertTask(task, true);
}
}
}
As you can see, I have a conditional statement in my hub method to handle if the hub call wasn't initiated from the client side portion of my app. This would be if an external app/service called my API. In such a situation, a SignalR connection and of course "ConnectionId" header value would not exist. In my case, though, I still would want to call the onInsertTask
method for all connected clients which informs them of the data change. This should never happen, but I just included it for completeness.