I am using SignalR to broadcast messages to all my clients. I need to trigger the broadcasting outside of my hub class i.e. something like below:
var broadcast = new chatHub();
broadcast.Send("Admin","stop the chat");
I am getting error message as:
Using a Hub instance not created by the HubPipeline is unsupported.
You need to use GetHubContext:
var context = GlobalHost.ConnectionManager.GetHubContext<chatHub>();
context.Clients.All.Send("Admin", "stop the chat");
This is described in more detail at http://www.asp.net/signalr/overview/signalr-20/hubs-api/hubs-api-guide-server#callfromoutsidehub.
A small update for those who might be wondering where the GlobalHost has gone. SignalR has been completely rewritten for .net core. So if you are using the SignalR.Core package (Difference between SignalR versions), you get an instance of SignalR hub context by injecting it into your service:
public class MyNeedyService
{
private readonly IHubContext<MyHub> ctx;
public MyNeedyService(IHubContext<MyHub> ctx)
{
this.ctx = ctx;
}
public async Task MyMethod()
{
await this.ctx.All.SendAsync("clientCall");
}
}
And in Startup.cs:
services.AddSignalR()/*.AddAzureSignalR("...")*/;
Microsoft docu is here: Send SignalR messages from outside the hub.
Related
Someone out there must have run into this already...
I created a WebApi solution with swagger implemented, full documentation, the whole 9 yards!
When I run my web api solution, see the swagger output (and I've tested the endpoints, all working fine)
I can see the swagger definition: https://localhost:5001/swagger/v1/swagger.json
Now, I want to consume this Api as a connected service on my web app.
So following every single tutorial online:
I go to my webapp
right click on Connected Services
Add Connected Service
Add Service Reference > OpenApi > add Url, namespace & class name
That generates a partial class in my solution (MyTestApiClient)
public parial class MyTestApiClient
{
// auto generated code
}
Next step, inject the service in Startup.cs
services.AddTransient(x =>
{
var client = new MyTestApiClient("https://localhost:5001", new HttpClient());
return client;
});
Then, inject the class into some class where it's consumed and this all works
public class TestService
{
private readonly MyTestApiClient _client; // this is class, not an interface -> my problem
public TestService(MyTestApiClient client)
{
_client = client;
}
public async Task<int> GetCountAsync()
{
return _client.GetCountAsync();
}
}
So everything up to here works. BUT, this generated OpenApi client doesn't have an interface which sucks for the purposes of DI and Unit Testing.
I got around this by creating a local interface IMyTestApiClient, added to the generated class (MyTestApiClient). I only have 1 endpoint in my WebApi so have to declare that on my interface.
public parial class MyTestApiClient : IMyTestApiClient
{
// auto generated code
}
public interface IMyTestApiClient
{
// implemented in generated MyTestApiClient class
Task<int> GetCountAsync();
}
services.AddTransient<IMyTestApiClient, MyTestApiClient>(x =>
{
IMyTestApiClient client = new MyTestApiClient("https://localhost:5001", new HttpClient());
return client;
});
public class TestService
{
private readonly IMyTestApiClient _client; // now injecting local interface instead of the generated class - great success
public TestService(IMyTestApiClient client)
{
_client = client;
}
public async Task<int> GetCountAsync()
{
return _client.GetCountAsync();
}
}
But this is a bad approach because it makes me manually create an interface and explicitly declare the methods I want to consume. Furthermore, every time my Api gets updated, I will have to tweak my local interface.
So question time:
How can I add an OpenApi Service Reference that automagically also generates an interface as well?
Thanks in advance for any help getting to a viable solution.
You may have already found the answer but I had the same issue and managed to resolve it by adding /GenerateClientInterfaces:true in the Options section for the OpenAPI reference in my .csproj:
<OpenApiReference Include="api.json" CodeGenerator="NSwagCSharp" Namespace="MyNamespace" ClassName="MyClassName">
<SourceUri>https://localhost:7040/swagger/v1/swagger.json</SourceUri>
<OutputPath>MyClient.cs</OutputPath>
<Options>/GenerateClientInterfaces:true</Options>
</OpenApiReference>
I have a (serverside) blazor app and I want to let users fill in a small form and press a button to create SignalR groups that they can then send messages to.
I have a Hub class that looks like this:
public class RoomHub : Hub
{
public async Task JoinRoomAsync(string groupName)
{
await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
}
public async Task LeaveRoomAsync(string groupName)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
}
public async Task BroadcastToRoomAsync(string groupName, string message)
{
await Clients.Group(groupName).SendAsync("OnMessage", message);
}
}
and a Service class that gets called from my blazor component, which looks like this:
public class RoomService : IRoomService
{
private ICosmosDbService _dbService;
private RoomHub _roomHub;
public RoomService(ICosmosDbService dbService, RoomHub roomHub)
{
this._dbService = dbService;
this._roomHub = roomHub;
}
public async Task<Room> CreateRoom(string name)
{
Room r = new Room();
r.Id = Guid.NewGuid().ToString();
r.Name = name;
await _dbService.AddItemAsync(r);
await _roomHub.JoinRoomAsync(r.Name);
return r;
}
public async Task SendToRoom(Room r, string message)
{
await _roomHub.BroadcastToRoomAsync(r.Name, message);
return;
}
}
When I add the RoomHub class to my services in Startup.cs and run my application, when I press the button to create a Group it tells me the Hub's Context variable is null and fails.
I've tried looking around for other ways to do this, and arrived at the conclusion that it has something to do with injecting an IHubContext<RoomHub> object instead, but the object this provides does not seem related at all to my Hub class and I can't use it to create groups directly because I don't have access to the ConnectionId I need to do so.
I feel like there's a gap between the Hub and HubContext that I do not understand. What is the correct way to create a SignalR Group, starting from a button press on a Blazor component?
Before you can access your Hub, you need to build and start your Hub connection using HubConnection and HubConnectionBuilder. This needs to include the url for your Hub and the handler methods for the data received from the Hub.
Start by adding a HubConnection field in your Service class.
private HubConnection _hubConnection;
Depending on your Service lifetime and other considerations, you can build your connection in the Service class constructor or it's own method. For an example, we'll add a StartConnectionAsync task.
public async Task StartConnectionAsync()
{
// Create the connection
_hubConnection = new HubConnectionBuilder()
.WithUrl(_hubUrl) // _hubUrl is your base Url + Hub Url
.Build();
// Add Handler for when a client receives a broadcast message
_hubConnection.On<string>("OnMessage", this.SomeEventHandler);
// Then you start the connection
await _hubConnection.StartAsync();
}
Without using a typed Hub, you'll call your Hub methods using magic strings. e.g.
await _hubConnection.SendAsync("JoinRoomAsync", groupName);
This should get you started. Based on what you posted above, I think this github repo is similar to what you're intending to do.
I want to send messages from the server (from a class, not a controller) via the SignalR Hub.
The hub works for messages originating from the client but not for messages from the server.
I've tried multiple methods of which non seem to work. For example, I tried retrieving the hub context using:
GlobalHost.ConnectionManager.GetHubContext<MyHub>()
with no success.
What is the best and up-to-date method of doing this in .NET Core?
Temporary Solution:
Having a websocket client inside the host api. Then making it connect to itself. This is not an ideal solution but works as a temporary fix.
You can inject the context in your class as service. Your class must be initialized via DI and added as a service. There is no difference between class or controller.
public class SomeClass
{
public IHubContext<ChatHub, IChatClient> _strongChatHubContext { get; }
public SomeClass(IHubContext<ChatHub, IChatClient> chatHubContext)
{
_strongChatHubContext = chatHubContext;
}
public async Task SendMessage(string message)
{
await _strongChatHubContext.Clients.All.ReceiveMessage(message);
}
}
You can also get service like following by injecting IHttpContextAccessor
var _strongChatHubContext = httpContextAccessor.HttpContext.RequestServices.GetRequiredService<IHubContext<ChatHub, IChatClient>>()
reference:
https://learn.microsoft.com/en-us/aspnet/core/signalr/hubcontext?view=aspnetcore-2.1
First you need to somewhere instantiate your hub (normally when your app is bootstrapping).
MyHub myHub = new MyHub();
Then on your class inject the context:
private readonly IHubContext<NotifyHub, ITypedHubClient> hubContext;
And in your class method just call the hub:
hubContext.Clients.All.yourHubMethod(yourPayload);
I'd like to know if an Hub method called from js client is
executed completely also if client disconnected during execution
For example..
public virtual void Join(int userId)
{
using (var context = new HubConnectionContext(this, RoomId, userId))
{
T workflow = GetWorkflow(context);
workflow.OnUserJoin();
}
}
I can be sure that the Dispose Method of HubConnectionContext is called also if client disconnect during using block?
I'm trying to get the context for a hub using the following:
var hubContext = GlobalHost.ConnectionManager.GetHubContext<SomeHub>();
The problem is that GlobalHost is not defined. I see it is part of the SignalR.Core dll. At the moment, I have the following in my project .json file, under dependencies:
"Microsoft.AspNet.SignalR.Server": "3.0.0-*"
If I add the latest available version of Core:
"Microsoft.AspNet.SignalR.Server": "3.0.0-*",
"Microsoft.AspNet.SignalR.Core" : "2.1.2"
I get a whole bunch of errors because server and core are conflicting. If I change them to both use version "3.0.0-*", all the conflicts go away, but GlobalHost cannot be found. If I remove Server, and just user Core version 2.1.2 then GlobalHost works, but all the other things needing Server, obviously do not.
Any ideas?
IConnectionManager does not exist any more in SignalR for ASP.Net Core.
I've been using HubContext for getting access to a hub.
public class HomeController : Controller
{
private readonly IHubContext<LiveHub> _hubContext;
public HomeController(IHubContext<LiveHub> hubContext)
{
_hubContext = hubContext;
}
public void SendToAll(string message)
{
_hubContext.Clients.All.InvokeAsync("Send", message);
}
}
I'm using .net core 2.0.0 and SignalR 1.0.0-alpha1-final
Microsoft.AspNet.SignalR.Infrastructure.IConnectionManager is a DI injected service through which you can get the hub context...For example:
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Infrastructure;
using Microsoft.AspNet.Mvc;
public class TestController : Controller
{
private IHubContext testHub;
public TestController(IConnectionManager connectionManager)
{
testHub = connectionManager.GetHubContext<TestHub>();
}
.....
To use the hub in a backgroud service, in addition to controllers, you must use the IHostedService interface and get the hub by DI.
public class MyBackgroundService : IHostedService, IDisposable
{
public static IHubContext<NotifierHub> HubContext;
public MyBackgroundService(IHubContext<NotifierHub> hubContext)
{
HubContext = hubContext;
}
public Task StartAsync(CancellationToken cancellationToken)
{
//TODO: your start logic, some timers, singletons, etc
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
//TODO: your stop logic
return Task.CompletedTask;
}
public void Dispose()
{
}
}
Then you can call your hub from anywhere in your code from HubContext static field:
MyBackgroundService.HubContext.Clients.All.SendAsync("UpdateData", myData).Wait();
Learn more about IHostedService:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.1
You can create and start a timer in MyBackgroundService and call the hub in ElapsedEvent.
I needed to be able to access the Hub Context from outside the app request thread - because I was subscribing to NServicebus messages, and needed to be able to trigger a client function when I received a message.
Here's how I got it sorted:
public static IServiceProvider __serviceProvider;
then during startup configuration
app.UseServices(services =>
{
__serviceProvider = new ServiceCollection()
.BuildServiceProvider(CallContextServiceLocator.Locator.ServiceProvider);
});
Then anywhere else in the vNext asp.net application (any other thread)
var manager = Startup.__serviceProvider.GetRequiredService<IConnectionManager>();
var hub = manager.GetHubContext<ChatHub>();
Hope this helps!
I added some code to my Startup.cs to grab reference to the ConnectionManager which you can then use to do a GetHubContext at anytime from anywhere in your code. Similar to Nimo's answer but a little different, maybe simpler.
services.AddSignalR(options =>
{
options.Hubs.EnableDetailedErrors = true;
});
var provider = services.BuildServiceProvider();
//Hold on to the reference to the connectionManager
var connManager = provider.GetService(typeof(IConnectionManager)) as IConnectionManager;
//Use it somewhere else
var hub = connManager.GetHubContext<SignalHub>();
I'm looking at SignalR source code and it seems that IHubContext is registered as a singleton.
Which means you get the same instance whenever you access it.
Which means you can simply save it in a static var and use it from whatever.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHubContext<MyHub> hubContext)
{
_staticVar = hubContext;
}
But be warned - it's an anti-pattern.