SignalR Get Clients outside of Hub not working - javascript

I am trying to call signalR from a controller, but it doesnt work.
Controller:
public ActionResult Index(){
var hub = GlobalHost.ConnectionManager.GetHubContext<ClientControl>();
hub.Clients.All.sendMessage("ahahhaaa");
//... return and staff
}
Hub
[Authorize]
[HubName("userTracking")]
public class ClientControl : Hub
{
public void RegisterConnection(String controller, String action)
{
}
public override Task OnConnected()
{
}
public override Task OnDisconnected()
{
return base.OnDisconnected();
}
}
Frontend:
$(document).ready(function () {
var trackhub = $.connection.userTracking;
trackhub.client.sendMessage = function (msg) {
alert(msg);
}
$.connection.hub.logging = true;
$.connection.hub.start();
});
In the controller it doesnt throw an error. When I debug and pause it, hub object exists, but when I perform it, it sends no message to the frontend. If I call the same method from the hub - works as a charm.
Any idea, what can be the root cause of the problem? If needed - write in the comments I will provide more info.

Server
public class ChatHub : Hub
{
public int TryAddNewUser(string userName)
{
//some logic...
Clients.All.AddUserToUserList(id, userName);
return id;
}
public void AddNewMessageToPage(int id, string message)
{
//some logic...
Clients.All.addNewMessageToPage(u.Login, message);
}
}
Client
$(document).ready(function () {
//first need register client methods
var chat = $.connection.chatHub;
chat.client.addUserToUserList = function (id, login) {
//client logic for add new user
}
chat.client.addNewMessageToPage = function (login, message) {
//client logic for add new message from user
}
//second need start chat
$.connection.hub.start().done(function () {
chat.server.tryAddNewUser(login).done(function (id) {
alert("Added " + id)
});
});
});
Controller
public class ChatController : Controller
{
private IMyDataService _service;
public ChatController(IMyDataService s)
{
_service = s;
}
public ActionResult Index()
{
return View(new MyDataViewModel(_service));
}
}
Note, dynamic js file must be added with the same path
<script type="text/javascript" src="~/signalr/hubs"></script>
And i add following code to Startup.cs
using Owin;
using Microsoft.Owin;
[assembly: OwinStartup(typeof(ChatRoom.Startup))]
namespace ChatRoom
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
// Any connection or hub wire up and configuration should go here
app.MapSignalR();
}
}
}
About this see link

Please change the hub name in controller to work
Use
var hub = GlobalHost.ConnectionManager.GetHubContext<UserTracking>();
hub.Clients.All.sendMessage("ahahhaaa");
Instead of
var hub = GlobalHost.ConnectionManager.GetHubContext<ClientControl>();
hub.Clients.All.sendMessage("ahahhaaa");
Please note the change in Hub Class Name in GetHubContext<>.
Hope this helps.

Related

Spring STOMP - No matching message handler

My goal is to notify, if a player joins a team in real-time.
Since the player will be created in the controller, i want to notify the team about this change via socket.
This has been my first attempt in doing that:
#RestController
#RequestMapping("/api/secure/players/")
public class PlayerController {
#Autowired
private SimpMessagingTemplate simpMessageTemplate;
#PostMapping
public ResponseEntity<PlayerDTO> createPlayer() {
// ...
// data is being processed
// notify team about the change
simpMessageTemplate.convertAndSend("/teams/" + selectedTeam.getId() + "/", player);
return ...
}
}
#Controller
public class TeamSocket {
private static final Logger logger = LoggerFactory.getLogger(TeamSocket.class);
#MessageMapping("/teams/{team}")
#SendTo("/teams/{team}")
public void teamEvent(#DestinationVariable String team) {
logger.debug("Received message -> {}", team);
}
}
#Configuration
#ComponentScan
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
// I still dont fully understand this. Do i have to enable it? (It does not fix the issue).
// config.enableSimpleBroker("/teams/");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/api/secure/stomp/").withSockJS();
}
}
However, Spring tells me, that it was unable to map the request. What am i doing wrong?
DEBUG 29487 --- [nboundChannel-4] .WebSocketAnnotationMethodMessageHandler : Searching methods to handle SUBSCRIBE /teams/16 id=sub-0 session=0nkqv1km, lookupDestination='/teams/16'
DEBUG 29487 --- [nboundChannel-4] .WebSocketAnnotationMethodMessageHandler : No matching message handler methods.
DEBUG 29487 --- [nboundChannel-2] org.springframework.web.SimpLogging : Processing SUBSCRIBE /teams/16 id=sub-0 session=0nkqv1km
const client = new SockJS(`${REST_API}/api/secure/stomp/`);
const stompClient = Stomp.over(client);
stompClient.connect({}, (frame) => {
stompClient.subscribe(`/teams/${teamId}`, (message) => {
...
});
});
I see you are using path "/teams/" + selectedTeam.getId() + "/" in your convertAndSend method while you are subscribing to /teams/${teamId}. Do you need to add additional slash while subscribing or you may need to remove additional slash from convertAndSend?

SignalR methods in WebApi controller not called in specific setup

Well, I have a signalR hub:
public class ReportHub : Hub
{
private static IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<ReportHub>();
public void SendMessage(string text)
{
Clients.All.sendMessage(text);
}
public static void ServerSendMessage(string text)
{
hubContext.Clients.All.sendMessage(text);
}
}
Also I have the client code in js, on some view
report.client.sendMessage = message => {
alert('message from server: '+ message);
}
And I have webapi action, like this:
[HttpGet]
[Route("api/Report/test")]
public int GetTest()
{
ReportHub.ServerSendMessage("message");
return 42;
}
When I open the view with signalR-catching js code in one browser, and in another browser window requesting the webapi action, by typing http://../api/report/test - all working, and alert is appearing
But when I calling webapi action via postman, or any other rest client, no effect at all, report.client.sendMessage = message => {
alert('message from server: '+ message);
} - not working
Can anyone help?
ReportHub.ServerSendMessage("message");
It is wrong.
You should return ReportHubContext for the connection before pushing the data to clients;
[HttpGet]
[Route("api/Report/test")]
public int GetTest()
{
var hubContext = GlobalHost.ConnectionManager.GetHubContext<ReportHub>();
hubContext.ServerSendMessage("message");
return 42;
}

How to communicate with spring websocket

I am trying to build an application where the server will keep pushing message to the client in some interval.
I have a simple html file like this.
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<script src="sockjs/sockjs.js"></script>
<script src="stomp/stomp.js"></script>
<div ng-app="myApp" ng-controller="myCtrl">
<button ng-click='connect()'>hi</button>
</div>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.connect = function() {
var socket = new SockJS('http://localhost:8099/myws');
$scope.stompClient = Stomp.over(socket);
$scope.stompClient.connect({}, function (frame) {
console.log('Connected:bhabani ' + frame);
$scope.stompClient.subscribe('http://localhost:8099/topic/jobconfig', function (wsdata) {
console.log("helloooooooooooooooooooooooooooooooooooooooooooooooooo");
console.log(wsdata);
});
});
}
});
</script>
I opened the html file in the file system.
file:///export/data1/test-ws.html in the browser.
Now i have a spring web socket like this.
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Autowired
private GreetingController gc;
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/myws").setAllowedOrigins("*").withSockJS();
new Thread(gc).start();
}
}
Have a greeting controller like this, which should push a message to the topic in some internal
#Component
public class GreetingController implements Runnable{
#Autowired
private SimpMessagingTemplate template;
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
while(true){
try {
System.out.println("Sending");
Thread.sleep(1000);
template.convertAndSend("/topic/jobconfig", new Greeting("hi"));
} catch (Exception e) {
e.printStackTrace();
}
}
}
Where i press the connect button i can see connection is established.
But after that i do not see any message coming in the browser which should be pushed from the server.
I am expecting the 'helloooooooooooo' message should be printed in my browser console in each interval.
Change URL in stomp client subscribe code from this http://localhost:8099/topic/jobconfig to this /topic/jobconfig.
$scope.stompClient.subscribe('/topic/jobconfig', function(wsdata) {
console.log("hello");
console.log(wsdata);
});

Use SignalR as Broadcaster for EventBus Events

I recently started a new project with the AspBoilerplate (Abp) and to use SignalR as some kind of broadcasting mechanism to tell the connected clients if some records in the databse changed or were added or removed.
If i use the SignalR Hub as a proxy to my AppService everything works ok and the client is notified
public class TestHub : Hub
{
IMyAppService = _service
public TestHub(IMyAppService service)
{
_service = service;
}
public void CreateEntry(EntryDto entry)
{
_service.Create(entry);
Clients.All.entryCreated(entry);
}
}
But if i try to leverage the advantages of the EventBus of Abp so i implemented my AppSevice to send Events to the EventBus:
class MyAppService : ApplicationService, IMyAppService
{
public IEventBus EventBus { get; set; }
private readonly IMyRepository _myRepository;
public LicenseAppService(ILicenseRepository myRepository)
{
EventBus = NullEventBus.Instance;
_myRepository = myRepository;
}
public virtual EntryDto CreateLicense(EntryDto input)
{
var newEntry = Mapper.Map<EntryDto >(_myRepository.Insert(input));
EventBus.Trigger(new EntryCreatedEventData { Entry = newEntry});
return newEntry;
}
}
Then i tried to use the hub directly as EventHandler, but this failed because abp creates its own instance of the EventHandler classes whenever it needs to handle an event. But here the code just for completeness:
public class TestHub : Hub,
IEventHandler<EntryCreatedEventData>
{
public void Handle(EntryCreatedEventData data)
{
Clients.All.entryCreated(data.Entry);
}
}
After this i created a seperate Listener class and tried to use the hub context like this and use an pretty empty Hub:
public class TestHub : Hub
{
}
public class EntryChangeEventHandler : IEventHandler<EntryCreatedEventData>
{
private IHubContext _hubContext;
public EntryChangeEventHandler()
{
_hubContext = GlobalHost.ConnectionManager.GetHubContext<TestHub>();
public void Handle(EntryCreatedEventData data)
{
_hubContext.Clients.All.entryCreated(eventData.Entry);
}
}
In the last solution everything runs up to the line
_hubContext.Clients.All.entryCreated(eventData.Entry);
but on the client side in my javascript implementation the method is never called. The client side (based on DurandalJs) didn't change between using the Hub as proxy and the new way i want to go.
Client side plugin for working with signalr
define(["jquery", "signalr.hubs"],
function ($) {
var myHubProxy
function connect(onStarted, onCreated, onEdited, onDeleted) {
var connection = $.hubConnection();
myHubProxy = connection.createHubProxy('TestHub');
connection.connectionSlow(function () {
console.log('We are currently experiencing difficulties with the connection.')
});
connection.stateChanged(function (data) {
console.log('connectionStateChanged from ' + data.oldState + ' to ' + data.newState);
});
connection.error(function (error) {
console.log('SignalR error: ' + error)
});
myHubProxy .on('entryCreated', onCreated);
myHubProxy .on('updated', onEdited);
myHubProxy .on('deleted', onDeleted);
connection.logging = true;
//start the connection and bind functions to send messages to the hub
connection.start()
.done(function () { onStarted(); })
.fail(function (error) { console.log('Could not Connect! ' + error); });
}
return signalr =
{
connect: connect
};
});
view using the plugin:
define(['jquery', 'signalr/myHub],
function ($, myHubSR) {
return function () {
var that = this;
var _$view = null;
that.attached = function (view, parent) {
_$view = $(view);
}
that.activate = function () {
myHubSR.connect(that.onStarted, that.onCreated, that.onEdited, that.onDeleted);
}
that.onStarted= function () {
//do something
}
that.onCreated= function (data) {
//do something
}
that.onEdited = function (data) {
//do something
}
that.onDeleted= function (data) {
//do something
}
}
});
So anyone got a clue why onCreated is never called when i call
_hubContext.Clients.All.entryCreated(eventData.Entry);
?
For testing if the signalR communication works at all i added a method that directly calls a client method. Calling this method the update is pushed to the client successfully. so i think the problem is wiht the remote call to all clients using the IHubContext any clues what could go wrong in the usage of IHubContext?
public class TestHub : Hub
{
public TestHub ()
:base()
{ }
public void Test()
{
this.Clients.All.entryCreated(new EntryDto());
}
}
First, have you registered EntryChangeEventHandler to DI? If not, implement also ITransientDependency interface for EntryChangeEventHandler.
Your problem might be related to serializing. It may not serialize eventData.Entry. You can try to send another DTO object.
Also, you can implement
IEventHandler<EntityChangedEventData<Project>>
in order to listen all changes in a Project entity (including insert, update and delete). Project is just a sample entity here.
For your first case, TestHub can not work if it's not registered to DI. You may implement also ITransientDependency for TestHub class. And you should make SignalR to get it from DI container. You can use such a class for it:
public class WindsorDependencyResolver : DefaultDependencyResolver
{
public override object GetService(Type serviceType)
{
return IocManager.Instance.IocContainer.Kernel.HasComponent(serviceType) ? IocManager.Instance.IocContainer.Resolve(serviceType) : base.GetService(serviceType);
}
public override IEnumerable<object> GetServices(Type serviceType)
{
return IocManager.Instance.IocContainer.Kernel.HasComponent(serviceType) ? IocManager.Instance.IocContainer.ResolveAll(serviceType).Cast<object>() : base.GetServices(serviceType);
}
}
And then set it on startup:
GlobalHost.DependencyResolver = new WindsorDependencyResolver();
Maybe my answer was a bit confusing :) I hope you can understand it.
After long searching in several directions i finally found a solution.
If you use a custom dependency Resolver in the HubConfiguration like i did. For example the implementation from hikalkan:
public class WindsorDependencyResolver : DefaultDependencyResolver
{
public override object GetService(Type serviceType)
{
return IocManager.Instance.IocContainer.Kernel.HasComponent(serviceType) ? IocManager.Instance.IocContainer.Resolve(serviceType) : base.GetService(serviceType);
}
public override IEnumerable<object> GetServices(Type serviceType)
{
return IocManager.Instance.IocContainer.Kernel.HasComponent(serviceType) ? IocManager.Instance.IocContainer.ResolveAll(serviceType).Cast<object>() : base.GetServices(serviceType);
}
}
you can no longer use
_hubContext = GlobalHost.ConnectionManager.GetHubContext<TestHub>();
unless you also set your GlobalHost.DependencyResolver to a instance of WindsorDependencyResolver or manually resolve a reference to IConnectionManager.
GlobalHost.DependencyResolver = new AutofacDependencyResolver(container);
IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
// A custom HubConfiguration is now unnecessary, since MapSignalR will
// use the resolver from GlobalHost by default.
app.MapSignalR();
or
IDependencyResolver resolver = new AutofacDependencyResolver(container);
IHubContext hubContext = resolver.Resolve<IConnectionManager>().GetHubContext<MyHub>();
app.MapSignalR(new HubConfiguration
{
Resolver = resolver
});

Edit /Update with Web API Repository Pattern

I'm trying to work out the very basics of updating my database using a Web API Controller that is backed by a repository pattern. So far I have everything working POST, GET, DELETE (Create, Read, Delete). But I'm missing the Update.
Below is my angular code, I'm not going to post the Angular Views/Templates, but just know that they do bind and they work just fine. My problem is only on the Edit View, where I try to update using the vm.save function. My save function works fine on the Angular side, but I'm not sure what to do on the Web API & Repository side. You will see that my code to get this working is very basic bare bones. I have all of the code pages from my project in a gist here:
All Files in Gist
Just in case you want to see the big picture, otherwise I will just put here the few pages where I am having trouble getting the Edit/Update methods to work in using http.put with Angular Controller, Web API Controller & Repository.
WORKING Angular Edit Controller:
function editFavoriteController($http, $window, $routeParams) {
var vm = this;
var url = "/api/favorites/" + $routeParams.searchId;
$http.get(url)
.success(function (result) {
vm.search = result[0];
})
.error(function () {
alert('error/failed');
})
.then(function () {
//Nothing
});
vm.update = function (id) {
var updateUrl = "/api/favorites/" + id;
$http.put(updateUrl, vm.editFavorite)
.success(function (result) {
var editFavorite = result.data;
//TODO: merge with existing favorites
//alert("Thanks for your post");
})
.error(function () {
alert("Your broken, go fix yourself!");
})
.then(function () {
$window.location = "#/";
});
};
};
NOT WORKING Web API Controller
public HttpResponseMessage Put(int id,[FromBody]Search editFavorite)
{
if (_favRepo.EditFavorite(id, editFavorite) && _favRepo.Save())
{
return Request.CreateResponse(HttpStatusCode.Created, editFavorite);
}
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
NOT WORKING Repository
public bool EditFavorite(int id, Search editFavorite)
{
try
{
var search = _ctx.Search.FirstOrDefault(s => s.SearchId == id);
search(editFavorite).State = EntityState.Modified;
return true;
}
catch
{
var item = "";
}
}
WORKING Interface
bool EditFavorite(int id, Search newSearch);
Again, my only problems are figuring out what to do for the update in the WebAPI FavoritesController and FavoritesRepository. I have example of how I have done everything else in the Gist, so I'm hoping someone might be able to help me out. I'm just hitting a wall of what I know how to do in Web API.
Fixed Code:
public HttpResponseMessage Put(int id,[FromBody]Search editFavorite)
{
if (_favRepo.EditFavorite(id, editFavorite))
{
_favRepo.Save()
return Request.CreateResponse(HttpStatusCode.Created, editFavorite);
}
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
I am also posting code which should work fine for handling edit on server side using WEB API and Repository Pattern.
WebAPI Controller:
public HttpResponseMessage Put(int id,[FromBody]Search editFavorite)
{
if (!ModelState.IsValid || id != editFavorite.Id)
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
db.EditFavorite(editFavorite);
try
{
db.Save();
}
catch (DbUpdateConcurrencyException)
{
if (!db.SearchExists(id))
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
else
{
throw;
}
}
return Request.CreateResponse(HttpStatusCode.Created, editFavorite);
}
Repository Method:
public void EditFavorite(Search editFavorite)
{
db.Entry(editFavorite).State = EntityState.Modified;
}
public void Save()
{
db.SaveChanges();
}
public bool SearchExists(int id)
{
return db.Search.Count(e => e.Id == id) > 0;
}
Modify Interface:
void Save();
void EditFavorite(Search newSearch);
bool SearchExists(int id);
Edit:
I have made some changes so that only operations that are carried out on your db context is done in repository layer (Data Layer) and the error checking is done in the WEB API Controller.
Suggestion:
You should inherit IDisposable on the interface and implement it your repository class so that your entities are properly disposed...
public interface IFavoritesRepository : IDisposable
{
// code here
}
public class FavoritesRepository : IFavoritesRepository
{
// code here
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
db.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}

Categories

Resources