(I'm new to Signalr)
I'm developing an web application which uses Signalr-core to update the page in realtime.
The problem i walk into is that when i run multiple clients the method i'm running wil run as many times at once as there are clients.
so i want to know if there is any way to make the first client call the hub method and then keep it running and broadcasting to all connected clients.
this is what i'm doing with my client:
connection.on('update', (id, Color) => {
var x = document.getElementById(id);
if (Color === "green" && x.classList.contains("red"))
{
//console.log("green");
x.classList.remove("red");
x.classList.add("green");
}
else if (Color === "red" && x.classList.contains("green"))
{
//console.log("Red");
x.classList.remove("green");
x.classList.add("red");
}});
connection.start()
.then(() => connection.invoke('updateclient', updating));
and this is what i'm doing with my hub:
public void UpdateClient(bool updating)//this must run only once
{
while (updating == true)
{
Thread.Sleep(2000);
foreach (var item in _context.Devices)
{
IPHostEntry hostEntry = Dns.GetHostEntry(item.DeviceName);
IPAddress[] ips = hostEntry.AddressList;
foreach (IPAddress Ip in ips)
{
Ping MyPing = new Ping();
PingReply reply = MyPing.Send(Ip, 500);
if (reply.Status == IPStatus.Success)
{
Color = "green";
Clients.All.InvokeAsync("update", item.DeviceID, Color);
break;
}
else
{
Color = "red";
Clients.All.InvokeAsync("update", item.DeviceID, Color);
}
}
}
}
}
please let me know if i'm unclear about something.
and thank you in advance.
As I mention in the comment, you can invoke UpdateClient method on first appearance of any client. The option I came up with is as follows;
It is quite common to use a static list for clients connected to hub
public static class UserHandler
{
public static List<string> UserList = new List<string>();
}
In your hub, override OnConnected and OnDisconnected methods to add/remove clients to/from the list and define the very first client connected to hub for your purpose.
public class MyHub : Hub
{
private static IHubContext context = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
public override Task OnConnected()
{
//Here check the list if this is going to be our first client and if so call your method, next time because our list is filled your method won't be invoked.
if(UserHandler.UserList.Count==0)
UpdateClient(true);
UserHandler.UserList.Add(Context.ConnectionId)
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
UserHandler.UserList.RemoveAll(u => u.ConnectionId == Context.ConnectionId);
return base.OnDisconnected(stopCalled);
}
}
I didn't take your business logic or your needings into consideration, this is just a general idea. For example, you might want to add some logic for when all the clients goes off, you should stop the loop inside UpdateClient with using updating flag.
Related
I am using SignalR to transfer data on a website. But SignalR should only be able to send data for a period of time and if the time period has passed the connection should be killed.
The Stop-Function $.connection.hub.stop() is cancelled if a request is still pending and is not completed. But this request should be forced to cancel no matter how much data has been sent.
How can I kill a SignalR-Connection?
As you can see in this Microsoft Documentation about Timeout and keepalive settings you can define the DisconnectTimeout in the options.
Example:
protected void Application_Start(object sender, EventArgs e)
{
// Make long-polling connections wait a maximum of 110 seconds for a
// response. When that time expires, trigger a timeout command and
// make the client reconnect.
GlobalHost.Configuration.ConnectionTimeout = TimeSpan.FromSeconds(110);
// Wait a maximum of 30 seconds after a transport connection is lost
// before raising the Disconnected event to terminate the SignalR connection.
GlobalHost.Configuration.DisconnectTimeout = TimeSpan.FromSeconds(30);
// For transports other than long polling, send a keepalive packet every
// 10 seconds.
// This value must be no more than 1/3 of the DisconnectTimeout value.
GlobalHost.Configuration.KeepAlive = TimeSpan.FromSeconds(10);
RouteTable.Routes.MapHubs();
}
Edit: Since you want to kill the connection from the client no matter what, you are talking about a CancellationToken behavior but unfortunately this is still not supported in SignalR as you can see here and here, the team wants to do that to SignalR but still there is no news about it.
Please read this microsoft document about Hub lifetime event. You can change the default values for these settings, set them in Application_Start in your Global.asax file. But this way you can't full control client side. So you use javascript setTimeout function and pass the time form server end when a new user connect.it's may be GlobalHost.Configuration.DisconnectTimeout or any time you want. I give a full example with demo project. Actually i use this logic in a very large ticketing system for real-time holding ticket.(please read all inline comment)
Model:
public class MyModel
{
public int Id { get; set; }
public string Name { get; set; }
public static string Send(MyModel my)
{
//Do Somthing
return $"Data Sending to {my.Name}...";
}
public static string Stop(string name)
{
//Do Somthing
return $"ForceStop {name}.";
}
public static string Delete()
{
//Do Somthing
return "Deleted";
}
}
Hub:
[HubName("myHub")]
public class MyHub : Hub
{
int connectionTimeOut = 10;//sec
[HubMethodName("connect")]
public void Connect()
{
//apply logic if any when user connected or reload page
//set connection Time Out as you need
connectionTimeOut= 10;// GlobalHost.Configuration.DisconnectTimeout
Clients.Client(Context.ConnectionId).onNewUserConnected(connectionTimeOut);
}
[HubMethodName("startSendingServer")]
public void StartSending(int id, string name)//pass anything you need
{
//apply logic if any when start sending data
var my = new MyModel
{
Id = id,
Name = name
};
var status = MyModel.Send(my);//example
var result = new
{
status,
name
};
Clients.Client(Context.ConnectionId).startSendingClient(result);
}
[HubMethodName("forceStopServer")]
public void ForceStop(string name)//pass anything you need
{
//apply logic if any when force stop sending data
var status = MyModel.Stop(name);
Clients.Client(Context.ConnectionId).forceStopClint(status);
}
public override Task OnDisconnected(bool stopCalled)
{
//apply logic if any when connection Disconnected
var status = MyModel.Delete();//example
if (stopCalled)
{
// status=String.Format("Client {0} explicitly closed the connection.", Context.ConnectionId)
//your code here
}
else
{
// status=String.Format("Client {0} timed out .", Context.ConnectionId);
//your code here
//Clients.Client(Context.ConnectionId).onUserDisconnected(status);
}
return base.OnDisconnected(stopCalled);
}
}
TestView:
<div class="row">
<div class="col-md-12">
<h1> Status: <span id="hubStatus"></span></h1>
<br />
<h4> Countdown : <span id="counter"></span></h4>
<br />
<button id="btnHub" class="btn btn-primary btn-lg">Start Sending Data</button>
</div>
</div>
#section scripts{
<script src="~/Scripts/app/hub.js"></script>
}
hub.js:
var proxyTimer = null;
var sendTimeLimit = 1;//sec
var sessionTime = sendTimeLimit * 1000;
$(function () {
var myProxy = $.connection.myHub;
$.connection.hub.start().done(function () {
registerServerEvents(myProxy);
});
clientMethods(myProxy);
});
function registerServerEvents(proxyHub) {
proxyHub.server.connect();
$(document).on("click", "#btnHub", function (e) {
$("#hubStatus").html("Sending..");
$("#btnHub").text("Count Down Start...");
//Logic Before start sending data.
var id = 1;
var name = "AzR";
proxyHub.server.startSendingServer(id,name);
// $.connection.hub.disconnected(function () {
// setTimeout(function () { $.connection.hub.start(); }, 5000); // Restart connection after 5 seconds.
//});
$.connection.hub.disconnected(function () {
$("#hubStatus").html("Disconnected");// you can restart on here.
$("#btnHub").text("Stat Again after reload window");
});
});
}
function clientMethods(proxyHub) {
//proxyHub.on('onConnected', function (sendTimeLimit) {
// sendTimeLimit = sendTimeLimit;
//});
proxyHub.on('onNewUserConnected', function (serverItem) {
sendTimeLimit = serverItem;
sessionTime = sendTimeLimit * 1000;
});
proxyHub.on('startSendingClient', function (serverItem) {
//Logic after start sending data.
var name = serverItem.name;
var status = serverItem.status;
$("#hubStatus").html(status);
$("#counter").html(sendTimeLimit);
timeCounter();
startTimer(proxyHub, name );
});
proxyHub.on('forceStopClint', function (serverItem) {
clearClintPendingTask(serverItem);//Logic before proxy stop.
$("#btnHub").text("Force Stop...");
$.connection.hub.stop();
});
proxyHub.on('onUserDisconnected', function (serverItem) {
//Logic after proxy Disconnected (time out).
$("#hubStatus").html(serverItem);
$("#btnHub").text("Stat Again after reload window");
});
}
//Logic before proxy stop.
function clearClintPendingTask(status) {
//do all you need
$("#hubStatus").html(status);
stopTimer();
}
function startTimer(proxyHub,data) {
stopTimer();
proxyTimer = setTimeout(function () {
proxyHub.server.forceStopServer(data);
}, sessionTime);
}
function stopTimer() {
if (proxyTimer) {
clearTimeout(proxyTimer);
proxyTimer = null;
}
}
function timeCounter() {
var counter = sendTimeLimit;
var interval = setInterval(function () {
counter--;
$("#counter").html(counter);
if (counter == 0) {
//Do something
$("#counter").html("Countdown ended!");
// Stop the counter
clearInterval(interval);
}
}, 1000);
}
(Tested)
You need to define a timeout. On the server you can set DisconnectTimeout, like this:
GlobalHost.Configuration.DisconnectTimeout = TimeSpan.FromMinutes(30);
https://zzz.buzz/2016/05/11/setting-timeout-for-signalr-for-easier-debugging/
Updated Edit, please see Option 3 below. All the others are relying on timeout, I posted a forced disconnect.
If you are trying a Force Disconnect -- you can get the list of the Connected Users and call the ForceLogOut Function on the server side, I saw this somewhere on code project, I hope it helps. If you only want to forceLogout/kill some of the users, just loop through and kill that connection only.
Server Side
public class User
{
public string Name { get; set; }
public HashSet<string> ConnectionIds { get; set; }
}
public class ExtendedHub : Hub
{
private static readonly ConcurrentDictionary<string, User> ActiveUsers =
new ConcurrentDictionary<string, User>(StringComparer.InvariantCultureIgnoreCase);
public IEnumerable<string> GetConnectedUsers()
{
return ActiveUsers.Where(x => {
lock (x.Value.ConnectionIds)
{
return !x.Value.ConnectionIds.Contains
(Context.ConnectionId, StringComparer.InvariantCultureIgnoreCase);
}
}).Select(x => x.Key);
}
public void forceLogOut(string to)
{
User receiver;
if (ActiveUsers.TryGetValue(to, out receiver))
{
IEnumerable<string> allReceivers;
lock (receiver.ConnectionIds)
{
allReceivers = receiver.ConnectionIds.Concat(receiver.ConnectionIds);
}
foreach (var cid in allReceivers)
{
// ***************** log out/KILL connection for whom ever your want here
Clients.Client(cid).Signout();
}
}
}
}
Client Side
// 1- Save your connection variable when you start it, and later on you can use it to stop.
var myHubProxy = $.connection.myHub
// 2- Use it when you need to stop it, IF NOT YOU WILL GET AN ERROR
myHubProxy.client.stopClient = function() {
$.connection.hub.stop();
};
// With a button for testing
$('#SomeButtonKillSignalr').click(function () {
$.connection.hub.stop();
});
Updated with Option 3: based on request... the other solutions rely on time out, but you can also force it directly by disposing the connection yourself
I opened the SignalR code up, and inside you can see DisposeAndRemoveAsync the actual termination of a client connection.
1- You can modify or call DisposeAndRemoveAsync with your connection.
2- Then call RemoveConnection(connection.ConnectionId);
public async Task DisposeAndRemoveAsync(HttpConnectionContext connection)
{
try
{
// this will force it
await connection.DisposeAsync();
}
catch (IOException ex)
{
_logger.ConnectionReset(connection.ConnectionId, ex);
}
catch (WebSocketException ex) when (ex.InnerException is IOException)
{
_logger.ConnectionReset(connection.ConnectionId, ex);
}
catch (Exception ex)
{
_logger.FailedDispose(connection.ConnectionId, ex);
}
finally
{
// Remove it from the list after disposal so that's it's easy to see
// connections that might be in a hung state via the connections list
RemoveConnection(connection.ConnectionId);
}
}
Caution, do any clean up yourself when this is done.
I have created the chat application with SignalR version 2.2.1 and I would like to create the information about user status - that means online/offline state.
I have tried use the method:
public override Task OnConnected()
{
var token = Context.QueryString["token"];
if (string.IsNullOrEmpty(token)) return Task.FromResult(0);
using (var dbContext = new AppDbContext())
{
dbContext.UserConnections.Add(new UserConnection { ConnectionKey = Context.ConnectionId, UserId = token.GetUserId() });
dbContext.SaveChanges();
}
Clients.Others.UserConnected(token.GetUserId());
return Task.FromResult(0);
}
public override Task OnDisconnected(bool stopCalled)
{
using (var dbContext = new AppDbContext())
{
var connection = dbContext.UserConnections.First(x => x.ConnectionKey == Context.ConnectionId);
var userId = connection.UserId;
if (connection == null) return Task.FromResult(0);
dbContext.UserConnections.Remove(connection);
dbContext.SaveChanges();
}
Clients.Others.UserDisconnected(userId);
return Task.FromResult(0);
}
I don't know how correctly work with disconnect method on the client - because when user refresh the browser automaticly is called the OnDisconnected event with parameter stopCalled=true - I can now send some information about disconnection this user and set the icon for offline but immediately is user connect again and the icon is set to the online state and the user experience is strange.
How can I manage the disconnect state on javascript client without blinking the online/offline status especialy thought refresh page?
I am new to SignalR. I need to send message from SignalR to all connected clients automatically with some delay without client input ?
The above process has to be repeated, while recursively?
Is it possible?
Without client input, the SignalR can send messages automatically to clients repeatedly?
This is my JavaScript cleint code:
$(function () {
var chat = $.connection.timehub;
$.connection.hub.start();
chat.client.broadcastMessage = function (current) {
var now = current;
console.log(current);
$('div.container').append('<p><strong>' + now + '</strong></p>');
}
};
and this is my Timehub
public class timehub : Hub
{
public void Send(string current)
{
current = DateTime.Now.ToString("HH:mm:ss:tt");
Clients.All.broadcastMessage(current);
System.Threading.Thread.Sleep(5000);
Send(current);
}
}
and this is my Owin Startup Class:
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR();
}
}
Could anyone provide me an solution for this?
If you will keep calling the Send() method recursively like you do right now, you will get a stackoverflow exception. Just wrap the code inside the method in a while(true) loop:
public class timehub : Hub
{
public void Send()
{
while(true)
{
var current = DateTime.Now.ToString("HH:mm:ss:tt");
Clients.All.broadcastMessage(current);
System.Threading.Thread.Sleep(5000);
}
}
}
I would suggest moving the Send() method to another thread, because the current thread will get stuck forever in this while loop.
I'm starting out with SignalR and I have a situation where I'm going to have a SignalR site that will be broadcasting messages to clients, but I also need an admin interface that will actually trigger those messages. The admin page will call server side methods that will, in turn, call client side Javascript methods for regular users. So I'm thinking I can either set up two separate hubs (one for admin, one for everybody else) or I can have methods in a single hub that can only be called by the admin that will check authorization.
But in addition to the authorization, I'd like to have SignalR not include admin methods or an admin hub in the generated Javascript proxy classes so that I'm not advertising their existence (again - this is NOT the only security, I will be checking authorization). Is there an attribute or property I can set on individual hubs or on methods within a hub that will suppress them from being included in the proxy (but still have them callable from Javascript)? I know you can set EnableJavaScriptProxies to false in your HubConfiguration, but that seems to be global and I'd like to keep the proxy for the stuff I do want the regular client to be using.
There is one trick using interfaces. As proxy will generate only public methods in proxy, you can create hub using interface like this:
public class MyHub : Hub, IMyHub
{
void IMyHub.NotGeneratedOnClient()
{
}
public void GeneratedOnClient()
{
}
}
NotGeneratedOnClient method will not be visible if you use object of type MyHub, you can access it only using interface. As method is not public proxy generator is not going to add it to client proxy
We don't have a way of excluding specific methods from the proxy today. You'd have to re-implement your own proxy generator that basically does what we do in our default impl but has knowledge of some attribute to skip generation of specific methods.
We can conceivable add this in a future version of SignalR. File an issue on github if you feel strongly about having this.
Here's the default implementation (it would have been easier if we made more methods virtual and non static).
https://github.com/SignalR/SignalR/blob/master/src/Microsoft.AspNet.SignalR.Core/Hubs/DefaultJavaScriptProxyGenerator.cs
Here is a modified DefaultJavaScriptProxyGenerator with the following changes:
It will exclude functions from Javascript proxy generation with a new [HubMethodExcludeFromProxy] attribute.
The private static functions have changed to protected virtual for future derivatives.
The GenerateProxy( ) function has an overload to include DocComments, but that was not caching the results like the non DocComments version. Now they both cache.
Two resources, Resources.DynamicComment_CallsMethodOnServerSideDeferredPromise and Resources.DynamicComment_ServerSideTypeIs were private to another assembly, so to get things to compile, I copied the text from the resource file directly. These two resources are only used if DocComments is true.
All of the DefaultJavaScriptProxyGenerator references were changed to CustomJavaScriptProxyGenerator except for one, which is used to locate the resource script Microsoft.AspNet.SignalR.Scripts.hubs.js, located in a different assembly.
First, you will need to update the dependency resolver to use the new CustomJavaScriptProxyGenerator for the IJavaScriptProxyGenerator interface. If you are using the default resolver, you can set up a custom resolver like this:
map.RunSignalR(
new HubConfiguration() {
Resolver = new CustomDependencyResolver()
}
);
And here is a custom resolver that derives from the DefaultDependecyResolver:
namespace Microsoft.AspNet.SignalR
{
public class CustomDependencyResolver : DefaultDependencyResolver
{
MyDependencyResolver() : base()
{
var proxyGenerator = new Lazy(() => new CustomJavaScriptProxyGenerator(this));
Register(typeof(IJavaScriptProxyGenerator), () => proxyGenerator.Value);
}
}
}
And finally, here is the new CustomJavaScriptProxyGenerator.cs file (the HubMethodExcludeFromProxyAttribute class is at the bottom):
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Mods by Brain2000
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.AspNet.SignalR.Json;
using Microsoft.AspNet.SignalR.Hubs;
using Newtonsoft.Json;
namespace Microsoft.AspNet.SignalR.Hubs
{
public class CustomJavaScriptProxyGenerator : IJavaScriptProxyGenerator
{
protected static readonly Lazy _templateFromResource = new Lazy(GetTemplateFromResource);
protected static readonly Type[] _numberTypes = new[] { typeof(byte), typeof(short), typeof(int), typeof(long), typeof(float), typeof(decimal), typeof(double) };
protected static readonly Type[] _dateTypes = new[] { typeof(DateTime), typeof(DateTimeOffset) };
protected const string ScriptResource = "Microsoft.AspNet.SignalR.Scripts.hubs.js";
protected readonly IHubManager _manager;
protected readonly IJavaScriptMinifier _javaScriptMinifier;
protected readonly Lazy _generatedTemplate;
protected readonly Lazy _generatedTemplateWithComments;
public CustomJavaScriptProxyGenerator(IDependencyResolver resolver) :
this(resolver.Resolve(),
resolver.Resolve())
{
}
public CustomJavaScriptProxyGenerator(IHubManager manager, IJavaScriptMinifier javaScriptMinifier)
{
_manager = manager;
_javaScriptMinifier = javaScriptMinifier ?? NullJavaScriptMinifier.Instance;
_generatedTemplate = new Lazy(() => GenerateProxy(_manager, _javaScriptMinifier, includeDocComments: false));
_generatedTemplateWithComments = new Lazy(() => GenerateProxy(_manager, _javaScriptMinifier, includeDocComments: true));
}
public string GenerateProxy(string serviceUrl)
{
serviceUrl = JavaScriptEncode(serviceUrl);
return _generatedTemplate.Value.Replace("{serviceUrl}", serviceUrl);
}
public string GenerateProxy(string serviceUrl, bool includeDocComments)
{
if (!includeDocComments) return GenerateProxy(serviceUrl); //use the includeDocComments: false cached version
serviceUrl = JavaScriptEncode(serviceUrl);
return _generatedTemplateWithComments.Value.Replace("{serviceUrl}", serviceUrl);
}
protected virtual string GenerateProxy(IHubManager hubManager, IJavaScriptMinifier javaScriptMinifier, bool includeDocComments)
{
string script = _templateFromResource.Value;
var hubs = new StringBuilder();
var first = true;
foreach (var descriptor in hubManager.GetHubs().OrderBy(h => h.Name))
{
if (!first)
{
hubs.AppendLine(";");
hubs.AppendLine();
hubs.Append(" ");
}
GenerateType(hubManager, hubs, descriptor, includeDocComments);
first = false;
}
if (hubs.Length > 0)
{
hubs.Append(";");
}
script = script.Replace("/*hubs*/", hubs.ToString());
return javaScriptMinifier.Minify(script);
}
protected virtual void GenerateType(IHubManager hubManager, StringBuilder sb, HubDescriptor descriptor, bool includeDocComments)
{
// Get only actions with minimum number of parameters.
var methods = GetMethods(hubManager, descriptor);
var hubName = GetDescriptorName(descriptor);
sb.AppendFormat(" proxies['{0}'] = this.createHubProxy('{1}'); ", hubName, hubName).AppendLine();
sb.AppendFormat(" proxies['{0}'].client = {{ }};", hubName).AppendLine();
sb.AppendFormat(" proxies['{0}'].server = {{", hubName);
bool first = true;
foreach (var method in methods)
{
if (!first)
{
sb.Append(",").AppendLine();
}
GenerateMethod(sb, method, includeDocComments, hubName);
first = false;
}
sb.AppendLine();
sb.Append(" }");
}
protected virtual string GetDescriptorName(Descriptor descriptor)
{
if (descriptor == null)
{
throw new ArgumentNullException("descriptor");
}
string name = descriptor.Name;
// If the name was not specified then do not camel case
if (!descriptor.NameSpecified)
{
name = JsonUtility.CamelCase(name);
}
return name;
}
protected virtual IEnumerable GetMethods(IHubManager manager, HubDescriptor descriptor)
{
return from method in manager.GetHubMethods(descriptor.Name).Where(md => md.Attributes.FirstOrDefault(a => (a.GetType() == typeof(HubMethodExcludeFromProxyAttribute))) == null)
group method by method.Name into overloads
let oload = (from overload in overloads
orderby overload.Parameters.Count
select overload).FirstOrDefault()
orderby oload.Name
select oload;
}
protected virtual void GenerateMethod(StringBuilder sb, MethodDescriptor method, bool includeDocComments, string hubName)
{
var parameterNames = method.Parameters.Select(p => p.Name).ToList();
sb.AppendLine();
sb.AppendFormat(" {0}: function ({1}) {{", GetDescriptorName(method), Commas(parameterNames)).AppendLine();
if (includeDocComments)
{
sb.AppendFormat(" /// Calls the {0} method on the server-side {1} hub.\nReturns a jQuery.Deferred() promise.", method.Name, method.Hub.Name).AppendLine();
var parameterDoc = method.Parameters.Select(p => String.Format(CultureInfo.CurrentCulture, " /// Server side type is {2}", p.Name, MapToJavaScriptType(p.ParameterType), p.ParameterType)).ToList();
if (parameterDoc.Any())
{
sb.AppendLine(String.Join(Environment.NewLine, parameterDoc));
}
}
sb.AppendFormat(" return proxies['{0}'].invoke.apply(proxies['{0}'], $.merge([\"{1}\"], $.makeArray(arguments)));", hubName, method.Name).AppendLine();
sb.Append(" }");
}
protected virtual string MapToJavaScriptType(Type type)
{
if (!type.IsPrimitive && !(type == typeof(string)))
{
return "Object";
}
if (type == typeof(string))
{
return "String";
}
if (_numberTypes.Contains(type))
{
return "Number";
}
if (typeof(IEnumerable).IsAssignableFrom(type))
{
return "Array";
}
if (_dateTypes.Contains(type))
{
return "Date";
}
return String.Empty;
}
protected virtual string Commas(IEnumerable values)
{
return Commas(values, v => v);
}
protected virtual string Commas(IEnumerable values, Func selector)
{
return String.Join(", ", values.Select(selector));
}
protected static string GetTemplateFromResource()
{
//this must remain "DefaultJavaScriptProxyGenerator" because the resource "Microsoft.AspNet.SignalR.Scripts.hubs.js" lives there
using (Stream resourceStream = typeof(DefaultJavaScriptProxyGenerator).Assembly.GetManifestResourceStream(ScriptResource))
{
var reader = new StreamReader(resourceStream);
return reader.ReadToEnd();
}
}
protected virtual string JavaScriptEncode(string value)
{
value = JsonConvert.SerializeObject(value);
// Remove the quotes
return value.Substring(1, value.Length - 2);
}
}
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
public sealed class HubMethodExcludeFromProxyAttribute : Attribute
{
}
}
Now all you need to do is all a decorator to your hub methods, such as:
public class MyHub : Hub
{
[HubMethodExcludeFromProxy]
public void NotGeneratedOnClient()
{
}
public void GeneratedOnClient()
{
}
}
EDIT : There is an issue with dependency injection where if you have two different instances of a resolver, one in the GlobalHost.DependencyResolver and one in the Signalr configuration, it will cause remote methods to sometimes not work. Here is the fix:
//use only !ONE! instance of the resolver, or remote SignalR functions may not run!
var resolver = new CustomDependencyResolver();
GlobalHost.Configuration.DependencyResolver = resolver;
map.RunSignalR(
new HubConfiguration() {
Resolver = resolver;
}
);
Reference: https://github.com/SignalR/SignalR/issues/2807
I am trying to implement CometD in our application. But it is taking more time compared to the existing implementation in our project. The existing system is taking time in milliseconds where as CometD is taking 2 seconds to push the message.
I am not sure where I am going wrong. Any guidance will help me lot.
My code:
Java script at client side
(function($)
{
var cometd = $.cometd;
$(document).ready(function()
{
function _connectionEstablished()
{
$('#body').append('<div>CometD Connection Established</div>');
}
function _connectionBroken()
{
$('#body').append('<div>CometD Connection Broken</div>');
}
function _connectionClosed()
{
$('#body').append('<div>CometD Connection Closed</div>');
}
// Function that manages the connection status with the Bayeux server
var _connected = false;
function _metaConnect(message)
{
if (cometd.isDisconnected())
{
_connected = false;
_connectionClosed();
return;
}
var wasConnected = _connected;
_connected = message.successful === true;
if (!wasConnected && _connected)
{
_connectionEstablished();
}
else if (wasConnected && !_connected)
{
_connectionBroken();
}
}
// Function invoked when first contacting the server and
// when the server has lost the state of this client
function _metaHandshake(handshake)
{
if (handshake.successful === true)
{
cometd.batch(function()
{
cometd.subscribe('/java/test', function(message)
{
$('#body').append('<div>Server Says: ' + message.data.eventID + ':'+ message.data.updatedDate + '</div>');
});
});
}
}
// Disconnect when the page unloads
$(window).unload(function()
{
cometd.disconnect(true);
});
var cometURL = "http://localhost:8080/cometd2/cometd";
cometd.configure({
url: cometURL,
logLevel: 'debug'
});
cometd.addListener('/meta/handshake', _metaHandshake);
cometd.addListener('/meta/connect', _metaConnect);
cometd.handshake();
});
})(jQuery);
Comet service class
#Listener("/service/java/*")
public void processMsgFromJava(ServerSession remote, ServerMessage.Mutable message)
{
Map<String, Object> input = message.getDataAsMap();
String eventId = (String)input.get("eventID");
//setting msg id
String channelName = "/java/test";
// Initialize the channel, making it persistent and lazy
bayeux.createIfAbsent(channelName, new ConfigurableServerChannel.Initializer()
{
public void configureChannel(ConfigurableServerChannel channel)
{
channel.setPersistent(true);
channel.setLazy(true);
}
});
// Publish to all subscribers
ServerChannel channel = bayeux.getChannel(channelName);
channel.publish(serverSession, input, null);
}
Is there any thing I need to change in server side code.
You have made your channel lazy, so a delay in message broadcasting is expected (that is what lazy channels are all about).
Please have a look at the documentation for lazy channels.
If you want immediate broadcasting don't set the channel as lazy.