I've been pouring through documentation and forums trying to understand SignalR but I'm pretty stuck.
What I'm trying to achieve is, in a chat application: store messages outside of the Hub so that each time a user joins the chat, they can see all messages that had been sent before they joined.
So it seemed like an external class was the way to do that so I got that working with dependency injection
In ChatHub.cs
namespace SignalRChat.Hubs
{
public class ChatHub: Hub
{
public IChatStorage _chatStorage;
public ChatHub(IChatStorage chatStorage)
{
_chatStorage = chatStorage;
}
// and so on
And I have a method in ChatHub to send a message to chatStorage, but I'm confused on how to send back a list of all messages from chatStorage to ChatHub, or even to JavaScript. It seemed like a Controller was the way to do that but I'm not sure how to call the controller's methods:
namespace SignalRChat.Controllers
{
public class ChatController: Controller
{
private IHubContext<ChatHub> _hubContext;
public ChatController(IHubContext<ChatHub> hubContext)
{
_hubContext = hubContext;
}
public void Send(List<Message> messages)
{
// to do: something where chatStorage calls this method, then this
// method uses _hubContext.Clients.All.SendAsync
// But, how do I even call Send()???
}
}
}
Fundamentally I just don't understand how to wire everything up. SignalR is just really confusing.
I get how the simple server Hub and client JavaScript relationship works. But, then with dependency injection I don't get why
public ChatHub(IChatStorage chatStorage)
{
_chatStorage = chatStorage;
}
works. I didn't change any code to say like new ChatHub(new IChatStorage). Microsoft's docs even say that SignalR only calls default Hub constructors.
In Startup.cs nothing seems to specify that I want to call ChatHub with a new chatStorage:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddSignalR();
services.AddSingleton<IChatStorage, ChatStorage>();
services.AddSingleton<ChatController>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// omitted some default code
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapHub<ChatHub>("/chatHub");
});
}
So first question, how does that work? How does it know to pass an argument to the ChatHub constructor? I understand the services.AddSingleton part, just not how that gets "wired up".
Same thing with a controller class. How does the program know to pass an IHubContext object into its constructor? Where do you specify that?
Finally, how would you go about making this setup work? Current I'm trying to communicate from ChatHub->chatStorage->ChatController->ChatHub. And to do that I'm trying to pass a reference to chatController in chatStorage.
Not sure if it's clear what I'm asking. If anything I'm looking for a clear explanation on how these concepts all work together, rather than a specific solution to my code.
Thank you!
So first question, how does that work? How does it know to pass an argument to the ChatHub constructor? I understand the services.AddSingleton part, just not how that gets "wired up".
When SignalR instantiates your hub instance (the framework controls this, you don't), it will resolve any dependencies specified in your Hub's constructor. This is part of the dependency injection system that's part of .NET (as mentioned in the comments).
Same thing with a controller class. How does the program know to pass an IHubContext object into its constructor? Where do you specify that?
Same idea, but you didn't have to wire up IHubContext, that's something that AddSignalR does.
Finally, how would you go about making this setup work? Current I'm trying to communicate from ChatHub->chatStorage->ChatController->ChatHub. And to do that I'm trying to pass a reference to chatController in chatStorage.
It's not exactly clear to me what you want the interaction to be between the client and the server to both the hub and the controller.
What I'm trying to achieve is, in a chat application: store messages outside of the Hub so that each time a user joins the chat, they can see all messages that had been sent before they joined.
Going back to this original statement I can ask a few clarifying questions:
When the user writes a message in the chat room, are you calling the hub or a controller action (REST API)? That will determine where you need to inject the IChatStorage type. Once you receive a message, you'll stash the message in IChatStorage.
When the user joins the chat, they'll make a call to the server to retrieve all messages. This call will return messages stored in IChatStorage.
Assuming you want to use the hub for everything, you would expose methods on the hub do accomplish this. If you wanted to use REST API calls from the client, then you would use the controller.
Related
I am using SignalR and .Net 5.0 and leveraging Hub Filters to execute code on incoming Invokations to my SignalR Hub.
I am looking for a way to do the same thing with outgoing messages from the Hub to the Client but seem to be coming up with no options.
Perhaps alternatively, I would love to hook into and execute code specifically when the built in Ping Messages are sent out.
It looks like similar functionality used to be possible in the old version's HubPipeLineModule but I have not been able to find any way to achieve this in current SignalR. Is this possible?
HubLifetimeManager is responsible for sending messages to clients and it is possible to inject your custom one, right before registering SignalR:
builder.Services.AddSingleton(typeof(HubLifetimeManager<>), typeof(CustomHubLifetimeManager<>));
builder.Services.AddSignalR();
where
public class CustomHubLifetimeManager<THub> : DefaultHubLifetimeManager<THub> where THub : Hub
{
public override Task SendAllAsync(string methodName, object?[] args, CancellationToken cancellationToken = default)
{
//todo: do your stuff
return base.SendAllAsync(methodName, args, cancellationToken);
}
//todo: override rest of the methods
}
It works fine, however this approach looks a bit hacky for me.
As we know that Rebus provides Topic Based routing in addition to the familiar TypeBased routing although we are told that the TypeBased routing follows the same principle.
On my side however unfortunately I have not seen a good example on how to create a handler that processes messages published to a particular topic.
Suppose I publish my message as follows
var message=new Student { StudentID=90008,FirstName="Chilipo",LastName="Mjengo" };
await bus.Advanced.Topics.Publish("rebus_example_topic", message);
In another endpoint I have subscribed to the topic as follows
await bus.Advanced.Topics.Subscribe("rebus_example_topic");
My interest is to know how do I then implement the Handler that will process the messages published to the rebus_example_topic.
Regards
It's quite simple, actually 🙂 the preferred way of writing message handlers is to implement IHandleMessage<TMessage>, where TMessage is your message type.
In your case that would be
public class StudentHandler : IHandleMessages<Student>
{
public async Task Handle(Student message)
{
// handle your message in here
}
}
How you then activate your message handler depends on which handler activator, you're using. The "handler activator" is what you use to instantiate message handlers, so you just need to register your handler in that to make it available to Rebus.
Some container integrations even come with additional help in the form of registration extensions, so e.g. if you're using Autofac, you can register your handler like this:
containerBuilder.RegisterHandler<StudentHandler>();
In SignalR 2 you could do something like this (taken from my blog):
var stockTickerHub = GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>();
That allows you to get a reference to the SignalR hub from outside the hub (e.g. from a stock ticker thread).
This does not seem to be available in SignalR 3. How do you achieve the equivalent functionality in the new and upcoming SignalR?
I asked the same thing to the creator of SignalR, David Fowler on Jabbr, a forum where the creators of SignalR and the architects of ASP.NET 5 hang on from time to time, and his answer to this question was to use dependency injection.
While I haven't tried it yet with SignalR 3, I am pretty sure you can inject an instance of ConnectionManager that implements IConnectionManager in your class, and use it just like you would use GlobalHost to resolve your hub context.
Again, I have not done this with SignalR3, but I hope this will get you a little closer to finding a solution.
I put together a sample for using SignalR 2 with Autofac. (In this repo I use Autofac to inject dependencies in my hub, but also to inject instances of a ConnectionManager in other classes to get the hub context).
Hope this helps. Best of luck!
Dependency injection is indeed the way and works.
Example:
public class ChatController : Controller
{
readonly IConnectionManager _connectionManager;
public ChatController(IConnectionManager connectionManager)
{
_connectionManager = connectionManager;
}
public IActionResult Chat(string message)
{
IHubContext context = _connectionManager.GetHubContext<ChatHub>();
IConnection connection = _connectionManager.GetConnectionContext<PersistentConnection>().Connection;
context.Clients.All.NewMessage(message);
return new EmptyResult();
}
}
From every example I have seen and the few SignalR 3 apps I have implemented, you no longer have a strongly typed reference to your hub. The current methodology connects to a hub via the hub's name and URL. The On generic method creates a subscription to broadcasts from that hub and the method name you provide.
HubConnection connection = new HubConnection(ServerURL);
IHubProxy hubProxy = connection.CreateHubProxy("StockTickerHub");
hubProxy.On<StockTickerMessage>("[Your method name here]", msg => {
//your UI update logic here
});
I managed to successfully get my RestTemplate client discover remote service using Eureka and forward calls to it using Ribbon as described in the documentation.
Basically, it was just a matter of adding the following annotations of my Application class and let the magic of Spring-Boot do the rest:
#Configuration
#ComponentScan
#EnableAutoConfiguration
#EnableDiscoveryClient
(PS: you noticed I'm using spring-cloud:1.0.0-SNAPSHOT-BUILD and not 1.0.0.M3 - but this doesn't seem to affect my problem).
When two service instances are started, the rest-template client successfully load balance requests between the two. However, the client won't fallback to the second instance if the first is stopped before the Eureka load balancer notices, instead an exception is thrown.
Hence my question: is there a way to configure the RestTemplate/Ribbon/Eureka stack to automatically retry the call to another instance if the one selected the first place is not available? Zuul proxy and feign clients do this "out of the box" so I believe the library holds the necessary features...
Any idea/hint?
Thx,
/Bertrand
The RestTemplate support on its own does not know how to do any retrying (whereas the Feign client and the proxy support in Spring Cloud does, as you noticed). I think this is probably a good things because it gives you the option to add it yourself. For instance, using Spring Retry you can do it in a simple declarative style:
#Retryable
public Object doSomething() {
// use your RestTemplate here
}
(and add #EnableRetry to your #Configuration). It makes a nice combination with #HystrixCommand (from Spring Cloud / Javanica):
#HystrixCommand
#Retryable
public Object doSomething() {
// use your RestTemplate here
}
In this form, every failure counts towards the circuit breaker metrics (maybe we could change that, or maybe it makes sense to leave it like that), even if the retry is successful.
I couldn't get it to work with both #HystrixCommand and #Retryable, regardless of order of annotations on #Configuration class or on #Retryable method due to order of interceptors. I solved this by creating another class with the matching set of methods and had the #HystrixCommand annotated methods delegate to the corresponding #Retryable method in the second class. You could probably have the two classes implement the same interface. This is kind of a pain in the butt, but until order can be configured, this is all I could come up with. Still waiting on a real solution from Dave Syer and the spring cloud guys.
public class HystrixWrapper {
#Autowired
private RetryableWrapper retryableWrapper;
#HystrixCommand
public Response doSomething(...) {
return retryableWrapper.doSomething(...);
}
}
public class RetryableWrapper {
#Autowired
private RestTemplate restTemplate;
#Retryable
public Response doSomething(...) {
// do something with restTemplate;
}
}
I've followed this link http://spring.io/guides/gs/messaging-stomp-websocket/ and got the app up and running.
What I wanted was a little more than that, I wanted to to be able to push the data back to the client without the client having to send any thing.
So I've setup a long running task with a listener similar to the below
GreetingController implements RunnableListener
and the RunnableListener has a method
public Greeting greeting(HelloMessage message);
The implementation of the method is to kick off a thread and then call the listener method..
I see the output on the console when that happens, but I don't see anything on the browser.
Could anyone please show me how to kick off a running task and let the server push the content to the browser using Spring instead of poll (setTimeout stuff in javascript?)
Regards
Tin
What is this RunnableListener interface?
What is triggering this task - is it scheduled regularly?
Once the client has subscribed to a given topic (here, /topic/greetings), you can send messages to that topic whenever you want using a MessagingTemplate. For example, you could schedule this task and let it send messages regularly on a given topic:
#Service
public class GreetingService {
private SimpMessagingTemplate template;
#Autowired
public GreetingService(SimpMessagingTemplate template) {
this.template = template;
}
#Scheduled(fixedDelay=10000)
public void greet() {
this.template.convertAndSend("/topic/greetings", "Hello");
}
}
Check out the reference documentation for more details.