I have a WPF application which use SignalR to achieve publish/subscribe model.
When I used a WPF client to connect to the above application, the publish and callback worked successfully.
Then I created a ASP.Net client. I use a cross domain property of SignalR to connect to above WPF application.
It could connect to the application and call the method provided in the hub successfully.
However, when the WPF application call the method in the ASP.Net Client, it seems that that call cannot be reached to the client browser
(viewed in Firefox, the long polling does not return; break point cannot be reached even I have set the break point in the javascript callback function, and nothing could be displayed in the broswer).
I have included the following script in html
<script src="#Url.Content("~/Scripts/jquery-1.6.4.min.js")" type="text/javascript</script>
<script src="/Scripts/jquery.signalR-1.0.0-rc2.min.js" type="text/javascript"></script>
<script src="http://localhost:9999/signalr/hubs" type="text/javascript"></script>
The following is the javascript that I have used.
jQuery.support.cors = true;
myHub = $.connection.subscriberHub;
myHub.client.addMessage = function (msg, time) {
$("#message").prepend("<div>" + time + " " + msg + "</div>");
};
$.connection.hub.url = 'http://localhost:9999/signalr';
$.connection.hub.start();
The below is the server code in the WPF application:
public partial class App : Application
{
private IDisposable app;
private void Application_Startup(object sender, StartupEventArgs e)
{
string url = "http://localhost:9999";
app = WebApplication.Start<Startup>(url);
}
private void Application_Exit(object sender, ExitEventArgs e)
{
if (app != null)
{
app.Dispose();
}
}
}
class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapHubs();
}
}
And I send the message when the WPF application clicked a button:
private void btn_sendMsg_Click(object sender, RoutedEventArgs e)
{
var context = GlobalHost.ConnectionManager.GetHubContext<SubscriberHub>();
DateTime sentTime = DateTime.Now;
context.Clients.Group("subscriber").addMessage(tb_message.Text, sentTime);
MessageList.Insert(0,string.Format("{0:yyyy-MM-dd HH:mm:ss} {1}", sentTime, tb_message.Text));
}
The following is the hub that I have defined:
public class SubscriberHub : Hub
{
string group = "subscriber";
public Task Subscribe()
{
return Groups.Add(Context.ConnectionId, group);
}
public Task Unsubscribe()
{
return Groups.Remove(Context.ConnectionId, group);
}
}
Is there any problem in the above code?
Don't you need to have the JavaScript client call Subscribe?
$.connection.hub.start().done(function () {
myHub.server.subscribe();
});
Alternatively you could modify SubscriberHub:
public class SubscriberHub : Hub
{
string group = "subscriber";
public override Task OnConnected()
{
return Groups.Add(Context.ConnectionId, group);
}
// ...
}
Related
I am trying to create a chat application in a Unity game. So basically in one instance of the game if someone sends a message , all the other open instances of the game should get the message.
I successfully created a self-hosted SignalR 2 server using this tutorial
The code for the console app is as follows:
using System;
using Microsoft.AspNet.SignalR;
using Microsoft.Owin.Hosting;
using Owin;
using Microsoft.Owin.Cors;
namespace SignalRSelfHost
{
class Program
{
static void Main(string[] args)
{
// This will *ONLY* bind to localhost, if you want to bind to all addresses
// use http://*:8080 to bind to all addresses.
// See http://msdn.microsoft.com/library/system.net.httplistener.aspx
// for more information.
string url = "http://localhost:8080";//a web application of type Startup is started at the specified URL (http://localhost:8080).
using (WebApp.Start(url))
{
Console.WriteLine("Server running on {0}", url);
Console.ReadLine();
}
}
}
class Startup
{// the class containing the configuration for the SignalR server ,which creates routes for any Hub objects in the project.
public void Configuration(IAppBuilder app)
{
app.UseCors(CorsOptions.AllowAll);
app.MapSignalR();
}
}
public class MyHub : Hub
{//the SignalR Hub class that the application will provide to clients.
public void Send(string name, string message)
{
Clients.All.addMessage(name, message);//clients will call to broadcast a message to all other connected clients.
}
}
}
I created a dummy Unity game as of now. There are the username input field and chat input field. Next to the chat input field there is a send button. So the person enters his/her name and enters something into the chat and all running instances of the game should receive the message , but I am not able to achieve that thing
The script for this is attached to a empty GameObject SignalRManager. The code is as follows:
using System.Collections.Generic;
using UnityEngine;
using Microsoft.AspNet.SignalR.Client;
using UnityEngine.UI;
using System;
using TMPro;
public class SignalRManager : MonoBehaviour
{
string url = "http://localhost:8080";
HubConnection connection;
[SerializeField] private GameObject ChatMessage;
[SerializeField] private GameObject UserName;
// Start is called before the first frame update
void Start()
{
connection = new HubConnection(url);
connection.Start();
connection.StateChanged += connection_StateChanged;
}
private void connection_StateChanged(StateChange state)
{
if(state.NewState== ConnectionState.Connected)
{
Debug.Log("Connected to Server");
}
if(state.NewState == ConnectionState.Disconnected)
{
Debug.Log("Disconnected from Server");
}
}
public void OnClickSendChatButton()
{
string message= ChatMessage.GetComponent<TMP_InputField>().text;
string userName= UserName.GetComponent<TMP_InputField>().text;
}
private void OnDisable()
{
connection.StateChanged -= connection_StateChanged;
}
}
I am able to connect to the server as the log message appears , but dont know what to write to send message to the server. Also how do I receive the message from the server as well
I tried to use a function called connection.Send(), but it is not accepting two arguments. I tried to look into many tutorials ,but many are for asp.netcore Signal r , but I need it for asp.net signalR because thats my requirement.
<<<<--------edit------>>>
I was able to send data to the server but I am not able to receive data from the server on my Unity game. The updated code is as follows:
using System.Collections.Generic;
using UnityEngine;
using Microsoft.AspNet.SignalR.Client;
using UnityEngine.UI;
using System;
using TMPro;
public class SignalRManager : MonoBehaviour
{
string url = "http://localhost:8080";
HubConnection connection;
IHubProxy hubProxy;
[SerializeField] private GameObject ChatMessage;
[SerializeField] private GameObject UserName;
[SerializeField] private TextMeshProUGUI OutputText;
// Start is called before the first frame update
void Start()
{
connection = new HubConnection(url);//Create a connection for the SignalR server
hubProxy = connection.CreateHubProxy("MyHub");//Get a proxy object that will be used to interact with the specific hub on the server.There may be many hubs hosted on the server, so provide the type name for the hub
connection.Start();
connection.StateChanged += connection_StateChanged;
OutputText.text = "";
}
private void OnReceivedMessageFromServer(string name, string message)
{
OutputText.text = OutputText.text+name + ":" + message + "\n";
Debug.Log(OutputText.text + name + ":" + message + "\n");
}
private void connection_StateChanged(StateChange state)
{
if(state.NewState== ConnectionState.Connected)
{
Debug.Log("Connected to Server");
}
if(state.NewState == ConnectionState.Disconnected)
{
Debug.Log("Disconnected from Server");
}
}
public void OnClickSendChatButton()
{
string message= ChatMessage.GetComponent<TMP_InputField>().text;
string userName= UserName.GetComponent<TMP_InputField>().text;
hubProxy.On<string, string>("addMessage", OnReceivedMessageFromServer);//register hub events (methods invoked by the hub). The following code registers a handler method for ChatMessage event.
hubProxy.Invoke("Send", userName, message);
}
private void OnDisable()
{
connection.StateChanged -= connection_StateChanged;
}
}
The callBack OnReceivedMessageFromServer is not invoked. Can anyone help me here?
I could not find any example of how to use the AuthenticationStateChanged in blazor.
My intention is that any page where i want to react to user login or logout i will use these
code. I could not find any example on how to implement the event. the one that i tried just keeps on firing for infinite times.
_CustomAuthProvider.AuthenticationStateChanged += AuhtenticationStateChanged;
private async void AuhtenticationStateChanged(Task<AuthenticationState> task)
{
//This keeps on executing in in loop.
}
I know this is old, but I would have liked an answer when I found it...
This is the code I use on a Blazor web assembly (dotnet 6.0). This is part of a scoped service that I can access through dependency injection from anywhere else in my application.
Notice the await(task) to retrieve the state in the event handler:
public AuthenticationService(AuthenticationStateProvider authenticationProvider, IProfileService profileService)
{
_profileService = profileService;
_authenticationProvider = authenticationProvider;
_authenticationProvider.AuthenticationStateChanged += AuthenticationStateChangedHandler;
// perform initial call into the event handler
AuthenticationStateChangedHandler(_authenticationProvider.GetAuthenticationStateAsync());
}
private bool _disposed = false;
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
_authenticationProvider.AuthenticationStateChanged -= AuthenticationStateChangedHandler;
}
}
public event AuthenticationChanged? AuthenticationChanged;
public AuthenticationState? AuthenticationState { get; private set; }
private async void AuthenticationStateChangedHandler(Task<AuthenticationState> task)
{
AuthenticationState = await (task);
if (IsAuthenticated)
{
// first load profile
await _profileService.LoadProfile(UserName!);
}
else
{
await _profileService.EmptyProfile();
}
// then update all listening clients, invoke the event
AuthenticationChanged?.Invoke(AuthenticationState);
}
I am a new user of signalR and Autofac. I am using signalR with ASP.NET Core Blazor Server and receiving the error listed below from a page that tries to connect to the hub. My Hub is strongly typed (IHubContext<Hub,Interface>) and is used within an IHostedService class implementation. It has a constructor that accepts an ILogger instance.
If I remove the constructor from the Hub implementation then the error does not occur. However, the IHubContext<Hub, IHub> appears not to be despatching to the clients in either case. The log message within the SendMotionDetection method on the hub is not displayed.
The official autofac documentation recommends installing the Autofac.SignalR NuGet package for integration with signalR. However, upon installing the package it is targeted for frameworks :.NETFramework,Version=v4.6.1, .NETFramework,Version=v4.6.2, .NETFramework,Version=v4.7, .NETFramework,Version=v4.7.1, .NETFramework,Version=v4.7.2, .NETFramework,Version=v4.8. I am targeting netcoreapp3.1 developing on MacOS.
Question:
How to register a strongly typed signalR Hub in AutoFac ASP.NET Core 3.1 for the purpose of injecting IHubContext<Hub, IHub> in IHostedService or BackgroundService?
Currently, the IHubContext<Hub, IHub> injected parameter is not sending the SendMotionDetection message to all clients, i.e. the console log message from the hubs message is not displayed. Yet, no exception is being thrown???
The error
fail: Microsoft.AspNetCore.SignalR.HubConnectionHandler[1]
Error when dispatching 'OnConnectedAsync' on hub.
Autofac.Core.DependencyResolutionException: An exception was thrown while activating WebApp.Realtime.SignalR.MotionHub.
---> Autofac.Core.Activators.Reflection.NoConstructorsFoundException: No accessible constructors were found for the type 'WebApp.Realtime.SignalR.MotionHub'.
at Autofac.Core.Activators.Reflection.DefaultConstructorFinder.GetDefaultPublicConstructors(Type type)
at Autofac.Core.Activators.Reflection.DefaultConstructorFinder.FindConstructors(Type targetType)
at Autofac.Core.Activators.Reflection.ReflectionActivator.ActivateInstance(IComponentContext context, IEnumerable`1 parameters)
at Autofac.Core.Resolving.InstanceLookup.CreateInstance(IEnumerable`1 parameters)
--- End of inner exception stack trace ---
at Autofac.Core.Resolving.InstanceLookup.CreateInstance(IEnumerable`1 parameters)
at Autofac.Core.Resolving.InstanceLookup.Execute()
at Autofac.Core.Resolving.ResolveOperation.GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, ResolveRequest request)
at Autofac.Core.Resolving.ResolveOperation.ResolveComponent(ResolveRequest request)
at Autofac.Core.Resolving.ResolveOperation.Execute(ResolveRequest request)
at Autofac.Core.Lifetime.LifetimeScope.ResolveComponent(ResolveRequest request)
at Autofac.ResolutionExtensions.TryResolveService(IComponentContext context, Service service, IEnumerable`1 parameters, Object& instance)
at Autofac.ResolutionExtensions.ResolveOptionalService(IComponentContext context, Service service, IEnumerable`1 parameters)
at Autofac.ResolutionExtensions.ResolveOptional(IComponentContext context, Type serviceType, IEnumerable`1 parameters)
at Autofac.ResolutionExtensions.ResolveOptional(IComponentContext context, Type serviceType)
at Autofac.Extensions.DependencyInjection.AutofacServiceProvider.GetService(Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetService[T](IServiceProvider provider)
at Microsoft.AspNetCore.SignalR.Internal.DefaultHubActivator`1.Create()
at Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher`1.OnConnectedAsync(HubConnectionContext connection)
at Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher`1.OnConnectedAsync(HubConnectionContext connection)
at Microsoft.AspNetCore.SignalR.HubConnectionHandler`1.RunHubAsync(HubConnectionContext connection)
Source code for the SignalR hub and Startup are listed below.
Within the ConfigureServices of Startup.cs, I have tried registering the SignalR Hub with autofac container registry but still getting the error. Interestingly, if I do not include a constructor for the SignalR hub the error does not occur. However, I am injecting an IHubContext into a background service and when sending a messages from the background service via the IHubContext it does not appear to be dispatching.
Interface
public interface IMotion
{
Task SendMotionDetection(MotionDetection message);
}
Hub
public class MotionHub : Hub<IMotion>
{
private ILogger<MotionHub> _logger;
MotionHub(ILogger<MotionHub> logger)
{
_logger = logger;
_logger.LogInformation("Motion SignalR Hub Created");
}
// send the motion detection event to all clients
public async Task SendMotionDetection(MotionDetection message)
{
_logger.LogInformation("MotionHub => SignalR Hub => SendMotionDetection");
await Clients.All.SendMotionDetection(message);
}
}
Startup
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public ILifetimeScope AutofacContainer { get; private set; }
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSignalR(o => o.EnableDetailedErrors = true);
services.AddHostedService<MqttListenerWorker>();
services.AddHostedService<ConsumerService>();
services.AddLogging();
}
// ConfigureContainer is where you can register things directly
// with Autofac. This runs after ConfigureServices so the things
// here will override registrations made in ConfigureServices.
// Don't build the container; that gets done for you by the factory.
public void ConfigureContainer(ContainerBuilder builder)
{
// Register your own things directly with Autofac here. Don't
// call builder.Populate(), that happens in AutofacServiceProviderFactory
// for you.
builder.RegisterModule(new MotionDetectionRepositoryModule());
builder.RegisterModule(new KafkaModule());
//builder.RegisterHubs(typeof());
builder.RegisterAssemblyTypes(typeof(MotionDetection).GetTypeInfo().Assembly);
builder.RegisterType<MotionHub>()
.AsSelf();
// builder.RegisterTypes(typeof(MotionHub).GetTypeInfo().Assembly)
// .Where(t => t.Name.EndsWith("Hub"))
// .As(typeof(Hub<MotionHub>))
// .ExternallyOwned();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
// app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<MotionHub>("/motionhub");
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
}
IHostedService
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Hosting;
using Confluent.Kafka;
using Confluent.Kafka.SyncOverAsync;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using WebApp.Data;
using WebApp.Data.Serializers.Contracts;
using WebApp.Kafka.Contracts;
using WebApp.Kafka.SchemaRegistry.Serdes;
using WebApp.Realtime.SignalR;
namespace WebApp.Kafka
{
public class ConsumerService : IHostedService, IDisposable
{
// At the time of writing Kafka Consumer isn't async so....
// making a long running background thread with a consume loop.
private Thread _pollLoopThread;
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private ConsumerConfig _consumerConfig = new ConsumerConfig();
private HashSet<string> _cameras { get; }
private string _topic;
private IHubContext<MotionHub, IMotion> _messagerHubContext;
private JsonDeserializer<MotionDetection> _serializer { get; }
private ILogger<ConsumerService> _logger;
// Using SignalR with background services:
// https://learn.microsoft.com/en-us/aspnet/core/signalr/background-services?view=aspnetcore-2.2
public ConsumerService(
IConfiguration config,
IHubContext<MotionHub, IMotion> messagerHubContext,
JsonDeserializer<MotionDetection> serializer,
ILogger<ConsumerService> logger
)
{
_logger = logger;
config.GetSection("Consumer").Bind(_consumerConfig);
// consider extension method for those settings that cannot be set in cnofig
if (_consumerConfig.EnablePartitionEof != null)
{
throw new Exception("shouldn't allow this to be set in config.");
}
_consumerConfig.EnableAutoCommit = false;
_topic = config.GetValue<string>("Topic");
_messagerHubContext = messagerHubContext;
_serializer = serializer;
_cameras = new HashSet<string>();
_cameras.Add("shinobi/group/monitor/trigger");
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("ConsumerService starting a thread to poll topic => {}...", _topic);
_pollLoopThread = new Thread(async () =>
{
try
{
var consumerBuilder = new ConsumerBuilder<string, MotionDetection>(_consumerConfig);
consumerBuilder.SetValueDeserializer(_serializer.AsSyncOverAsync());
using (var consumer = consumerBuilder.Build())
{
consumer.Subscribe(_topic);
try
{
while (!_cancellationTokenSource.IsCancellationRequested)
{
var consumerResult = consumer.Consume(_cancellationTokenSource.Token);
_logger.LogInformation("Consumer consumed message => {}", consumerResult.Message.Value);
if (_cameras.Contains(consumerResult.Message.Key))
{
// we need to consider here security for auth, only want for user
await _messagerHubContext.Clients.All.SendMotionDetection(consumerResult.Message.Value);
_logger.LogInformation("Consumer dispatched message to SignalR");
}
}
}
catch (OperationCanceledException) { }
consumer.Close();
_logger.LogInformation("Consumer closed, preparing to stop");
}
}
catch (Exception e)
{
_logger.LogCritical("Unexpected exception occurred in consumer thread");
_logger.LogError(e, "Consumer Error");
// update to take remdial action or retry to ensure consumer is available
// during lifetime
}
});
_pollLoopThread.Start();
_logger.LogInformation("Consumer thread started");
return Task.CompletedTask;
}
public async Task StopAsync(CancellationToken cancellationToken)
{
await Task.Run(() =>
{
_cancellationTokenSource.Cancel();
_pollLoopThread.Join();
});
_logger.LogInformation("Consumer stopped...");
}
public void Dispose()
{
_logger.LogInformation("Consumer disposed");
}
}
}
Think I have solved it.
The implementation of methods in the Hub class are invoked from client->server, so I would never see output from that because in this instance the server is pushing to the client.
For the time being I have changed the parameter to the method in IMotion interface to be a string and updated the code on the client blazor page to reflect a string parameter.
I also removed code that injects the Hub into autofac. I suspect that this is being handled by Microsoft DI automatically???
I think the issue might have been serialization/deserialization of object.
I have included the code below for the blazor page.
Next step is to work out how to serialize/deserialize object over signalR connection and also connect to the signalRHub after the page has been rendered instead of when it has been Initialized (executes twice!).
Blazor Page
#page "/"
#using System.Threading
#using System.Collections.Generic;
#using Microsoft.AspNetCore.SignalR.Client
#inject NavigationManager NavigationManager
#using WebApp.Data
<h1>Blazor Server App</h1>
<div>Latest message is => #_latestMessage</div>
<div id="scrollbox">
#foreach (var item in _messages)
{
<div>
<div>#item</div>
</div>
}
<hr />
</div>
#code {
private HubConnection hubConnection;
private string _latestMessage = "";
private List<string> _messages = new List<string>();
public bool IsConnected => hubConnection.State == HubConnectionState.Connected;
protected override async Task OnInitializedAsync()
{
var hubUrl = NavigationManager.BaseUri.TrimEnd('/') + "/motionhub";
// Uri uri = NavigationManager.ToAbsoluteUri("/motionhub");
try
{
hubConnection = new HubConnectionBuilder()
.WithUrl(hubUrl)
.Build();
hubConnection.On<string>("SendMotionDetection", ReceiveMessage);
await hubConnection.StartAsync();
Console.WriteLine("Index Razor Page initialised, listening on signalR hub url => " + hubUrl.ToString());
Console.WriteLine("Hub Connected => " + IsConnected);
}
catch (Exception e)
{
Console.WriteLine("Encountered exception => " + e);
}
}
private void ReceiveMessage(string message)
{
try
{
Console.WriteLine("Hey! I received a message");
_latestMessage = message;
_messages.Add(_latestMessage);
StateHasChanged();
}
catch (Exception ex)
{
Console.Error.WriteLine("An exception was encountered => " + ex.ToString());
}
}
}
We use SignalR library in our ASP.NET web application. The code looks as following:
Server:
[HubName("ticketsCounterHub")]
public class MassivePrintHub : Hub
{
public void PostTicketsCount(long count)
{
Clients.All.Send(count);
}
}
public class HubFactory
{
private HubFactory() {}
public static readonly HubFactory Current = new HubFactory();
public IHubProxy GetMassivePrintHubProxy()
{
var hubConnection = new HubConnection(ConfigUtils.GetRequiredSettingValue("adminPath"));
var hubProxy = hubConnection.CreateHubProxy("ticketsCounterHub");
hubConnection.Start().Wait();
return hubProxy;
}
}
Client (JavaScript):
MassivePrintApp.controller("ListController", function ($scope, Dates) {
var hubManager = (function () {
var massivePrintHub = $.connection.ticketsCounterHub;
$.connection.hub.start();
return { massivePrintHub: massivePrintHub };
} ());
hubManager.massivePrintHub.client.Send = function (ticketsCount) {
$scope.action.Quantity = ticketsCount;
$scope.$digest();
};
});
The key part of code is in MVC controller:
public FileResult PrintAction(int actionId, int count, DateTime actionDate, bool isThermo=false)
{
var ticketsCount = _ticketService.GetTicketsInStatusCount(actionId, actionDate, TicketStatusEnum.ToPrint);
HubFactory.Current.GetMassivePrintHubProxy().Invoke("PostTicketsCount", ticketsCount);
var stream = new MemoryStream();
xmlResponse.Save(stream);
stream.Flush();
stream.Position = 0;
return File(stream,ContentTypeEnum.XML.ToString(),String.Format("массовая {0} мероприятия {1} {2}шт.xml", isThermo?"термопечать":"печать", action.Artist,count));
}
As you can see, we have this line:
HubFactory.Current.GetMassivePrintHubProxy().Invoke("PostTicketsCount", ticketsCount);
And that causes the issue, that is whenever we call it one more instance of hub was added to "Requests" section on IIS.
I understand we already started hub in JavaScript code, but I'm not sure how can I use the existing connection or how to get rid of HubFactory or delete created hub instance.
And I don't understand why hub hangs on IIS.
Starting from a more simple example will help you a lot I guess. After that you can look into hosting your SignalR server differently (Console App or Windows Service) the basics won't change
(First installed SignalR: NuGet: install-package Microsoft.AspNet.SignalR)
I made a simple web-app example. The project has a Hub class:
using Microsoft.AspNet.SignalR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace SRTest
{
public class MassivePrintHub : Hub
{
private static IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<MassivePrintHub>();
// Can be called from your Javascript code
public void PostTicketsCount(long count)
{
Clients.All.Send(count);
}
// Can be called from your c# code
public static void Static_PostTicketsCount(long count)
{
hubContext.Clients.All.Send(count);
}
}
}
An Owin startup class:
using Microsoft.AspNet.SignalR;
using Microsoft.Owin;
using Owin;
[assembly: OwinStartup(typeof(SRTest.Startup))]
namespace SRTest
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
var hubConfiguration = new HubConfiguration();
hubConfiguration.EnableDetailedErrors = true;
app.MapSignalR(hubConfiguration);
}
}
}
Page (Razor just to be able to call a simulator which calls a c# class to post message from backend):
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>TEST PAGE</title>
<!--Reference the jQuery library. -->
<script src='Scripts/jquery-1.6.4.js'></script>
<!--Reference the SignalR library. -->
<script src='Scripts/jquery.signalR-2.2.0.js'></script>
<!--Reference the autogenerated SignalR hub script. -->
<script src="signalr/hubs"></script>
</head>
<body>
THIS IS A TEST PAGE
<!-- Call simulator (trigger event every 5 seconds) -->
#{SRTest.SendFromBackEnd.SimulateSend();}
<script>
$(function () {
var printHub = $.connection.massivePrintHub;
// when send event happens
printHub.client.send = function (count) {
console.log("Send " + count + " tickets");
};
$.connection.hub.start().done(function () {
console.log("Connected");
});
$.connection.hub.logging = true;
});
</script>
</body>
</html>
And I added a dummy class which triggers the event through hubcontext every 5 seconds.
using System.Threading;
using System.Web;
namespace SRTest
{
public class SendFromBackEnd
{
public static void SimulateSend()
{
new Thread(() =>
{
Thread.CurrentThread.IsBackground = true;
while (true)
{
MassivePrintHub.Static_PostTicketsCount(2);
Thread.Sleep(5000);
}
}).Start();
}
}
}
I added some loggings to the SignalR, add some debug points, it will help you understand the basics, then it will be much easier to build what you are planning to build.
EDIT
About the hanging request: As long as you have a client connected to your SignalR server with SSE or AJAX Long-Polling, you will have an ongoing request, which never finishes. (In case of AJAX Long-polling, it finishes for very short times and comes back). In the apps where I use only Javascript clients, I only see the request if a page is open where I am listening to events. If no page or static page open then no request.
In the apps where I am using .NET clients, as long as the two apps are running, and both Sartup classes executed, the request will always be there, even if no page open. (Since the .NET client is still listening to events.)
For more info: http://hanselminutes.com/291/damian-edwards-explains-the-realtime-web-for-aspnet-with-signalr
This is a Threading related issue. Try like this
Task.Run(() => connection.Start().ContinueWith(task =>
{
.....
})).Wait();
Here's my hub class:
public class ZaaloverzichtHub : Hub
{
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<ZaaloverzichtHub>();
private static readonly Lazy<ZaaloverzichtHub> instance = new Lazy<ZaaloverzichtHub>(() => new ZaaloverzichtHub());
public static ZaaloverzichtHub Instance { get { return instance.Value; } }
public void Send()//PartialViewResult zaaloverzicht)
{
// Call the broadcastMessage method to update clients.
context.Clients.All.broadcastMessage("test");
}
}
And here's the javascript in my view:
$(function () {
var connection = $.connection.ZaaloverzichtHub;
connection.client.broadcastMessage = function (zaaloverzicht) {
window.alert(zaaloverzicht);
}
$.connection.hub.start();
});
I'm calling the Send() method from my controller
var hub = ZaaloverzichtHub.Instance;
hub.Send();
The Send() method in the hub is being called however nothing is happening on the client side. I know the client is not being connected to the hub because when i make a method like this:
public override System.Threading.Tasks.Task OnConnected()
{
return base.OnConnected();
}
And place a breakpoint, the breakpoint is never hit.
The convention for SignalR is to use a lowercase naming scheme on the client side unless explicitly named using an attribute in c#
var connection = $.connection.zaaloverzichtHub;//note the case change on Z to z