I'm new to SignalR and trying to implement a notification when a particular event fires from an API .
What's tried:
Hub:
public class NotificationHub : Hub
{
private static IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
public static void Send( string content)
{
hubContext.Clients.All.addMessage(content);
}
}
Controller:
//static event from external API
public static void onTick(Tick TickData)
{
if(TickData.InstrumentToken == buy.InstrumentToken)
{
NotificationHub.Send(TickData.Bid);
}
}
What shall I use in the View to display the message which is triggered upon the condition?
View, tried :
$(document).ready(function () {
var conn = $.connection.NotificationHub;
conn.client.addMessage = function (message) {
alert(message);
};
});
Is there anything else needed to get this working?
Edit:
Ashley's answer got me closer and couple of things also was missing like below ,
connection.NotificationHubshould beconnection.notificationHub`
the order of the js files references should like
1 jquery-1.10.2.min.js
2 jquery.signalR-2.1.0.min.js
3 signalr/hubs
But now while executing it enters the .fail(function() and the console shows error ,
http://127.0.0.1:8080/signalr/negotiate?clientProtocol=1.5&connectionData=%5B%7B%22name%22%3A%22notificationhub%22%7D%5D&_=1515664348026 Failed to load resource: net::ERR_CONNECTION_REFUSE
Please advise. Thanks in advance.
Make sure you include the SignalR hubs source:
<script type="text/javascript" src="~/signalr/hubs"></script>
You just haven't started the connection for SignalR, add this:
$.connection.hub.start().done(function() {
//connection has started :-)
}).fail(function() {
//connection has failed to start :-(
});
Using your example it would look like this:
$(document).ready(function () {
var conn = $.connection.NotificationHub;
conn.client.addMessage = function (message) {
alert(message);
};
$.connection.hub.start().done(function() {
//connection has started :-)
alert("connected");
}).fail(function() {
//connection has failed to start :-(
alert("failed");
});
});
Related
I'm creating an ASP.NET MVC application which uses "SqlDependecy" and "SignalR" technologies to maintain real-time communication with the server based on database changes. It simply inspect a field value changes in specific database record and then display it on the browser.
The attempt works perfectly fine. But when I monitor the network requests through the browsers "Network" performance, the request count increases by 1 in every refresh of the page.
As in the image.
Initial page load only make one request.
First refresh after the initial load and then db change will lead to make 2 requests.
Second refresh after the initial load and then db change will lead to make 3 requests.
so on...
The js code I tried is given below.
It seams as an problem to me. If this is a real problem, Any advice on this will be highly appreciated. Thank you very much.
<script type="text/javascript">
$(function () {
var jHub = $.connection.journeyHub;
$.connection.hub.start();
jHub.client.ListenChange = function () {
getData();
}
jHub.client.ListenChange();
});
function getData() {
$.ajax({
url: 'GetValue',
type: 'GET',
dataType: 'json',
success: function (data) {
if (data == "pending") {
$("#box").css({ "background-color": "orange" });
}
else if (data == "deny") {
$("#box").css({ "background-color": "red" });
}
else if (data == "success") {
$("#box").css({ "background-color": "green" });
}
}
});
}
</script>
<div id="box" style="width:100px; height:100px; background-color: gray;"></div>
[Edit v1]
Here is my Controller where the event handler is located.
public class TravelController : Controller
{
SqlConnection link = new SqlConnection(ConfigurationManager.ConnectionStrings["linkTraveller"].ConnectionString);
// GET: Travel
public ActionResult Listen()
{
return View();
}
public ActionResult GetValue()
{
using (IDbConnection conn = link)
{
string query = #"SELECT [Status] FROM [dbo].[Journey] WHERE [Id]=1";
SqlCommand command = new SqlCommand(query, link);
SqlDependency sqlDep = new SqlDependency(command);
sqlDep.OnChange += new OnChangeEventHandler((sender, e) => sqlDep_OnChange(sender, e));
conn.Open();
string status = command.ExecuteScalar().ToString();
return Json(status, JsonRequestBehavior.AllowGet);
}
}
private void sqlDep_OnChange(object sender, SqlNotificationEventArgs e)
{
JourneyHub.Start();
}
}
Here is the Hub
public class JourneyHub : Hub
{
public static void Start()
{
var context = GlobalHost.ConnectionManager.GetHubContext<JourneyHub>();
context.Clients.All.ListenChange();
}
}
Off the top of my head, I would say you are not decrementing your trigger handlers, sql dependency triggers only fire once and then they are gone, you have to remember the remove the event handler for it or they just keep adding but, but I will know for sure if you can post your sql dependency trigger code.
Here is a sample from something I did many years ago, but the idea is still the same.
try
{
using (
var connection =
new SqlConnection(ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString))
{
connection.Open();
using (SqlCommand command = new SqlCommand(#"SELECT [Id]
,[FName]
,[LName]
,[DOB]
,[Notes]
,[PendingReview]
FROM [dbo].[Users]",
connection))
{
// Make sure the command object does not already have
// a notification object associated with it.
command.Notification = null;
SqlDependency dependency = new SqlDependency(command);
dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
if (connection.State == ConnectionState.Closed)
connection.Open();
command.ExecuteReader();
}
}
}
catch (Exception e)
{
throw;
}
}
private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
SqlDependency dependency = sender as SqlDependency;
if (dependency != null) dependency.OnChange -= dependency_OnChange;
//Recall your SQLDependency setup method here.
SetupDependency();
JobHub.Show();
}
I am seeing odd behavior with the code here.
Client-side (Javascript):
<input type="text" id="userid" placeholder="UserID" /><br />
<input type="button" id="ping" value="Ping" />
<script>
var es = new EventSource('/home/message');
es.onmessage = function (e) {
console.log(e.data);
};
es.onerror = function () {
console.log(arguments);
};
$(function () {
$('#ping').on('click', function () {
$.post('/home/ping', {
UserID: parseInt($('#userid').val()) || 0
});
});
});
</script>
Server-side (C#):
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Web.Mvc;
using Newtonsoft.Json;
namespace EventSourceTest2.Controllers {
public class PingData {
public int UserID { get; set; }
public DateTime Date { get; set; } = DateTime.Now;
}
public class HomeController : Controller {
public ActionResult Index() {
return View();
}
static ConcurrentQueue<PingData> pings = new ConcurrentQueue<PingData>();
public void Ping(int userID) {
pings.Enqueue(new PingData { UserID = userID });
}
public void Message() {
Response.ContentType = "text/event-stream";
do {
PingData nextPing;
if (pings.TryDequeue(out nextPing)) {
var msg = "data:" + JsonConvert.SerializeObject(nextPing, Formatting.None) + "\n\n";
Response.Write(msg);
}
Response.Flush();
Thread.Sleep(1000);
} while (true);
}
}
}
Once I've pressed ping to add a new item to the pings queue, the loop inside the Message method picks the new item up and issues an event, via Response.Write (confirmed using Debug.Print on the server). However, the browser doesn't trigger onmessage until I press ping a second time, and the browser issues another event; at which point the data from the first event reaches onmessage.
How can I fix this?
To clarify, this is the behavior I would expect:
Client Server
-------------------------------------------------------------------
Press Ping button
XHR to /home/ping
Eneque new item to pings
Message loop issues server-sent event
EventSource calls onmessage
This is what is actually happening:
Client Server
-------------------------------------------------------------------
Press Ping button
XHR to /home/ping
Eneque new item to pings
Message loop issues server-sent event
(Nothing happens)
Press Ping button again
New XHR to /home/ping
EventSource calls onmessage with previous event data
(While running in Chrome the message request is listed in the Network tab as always pending. I'm not sure if this is the normal behavior of server-sent events, or perhaps it's related to the issue.)
Edit
The string representation of the msg variable after Response.Write looks like this:
"data:{\"UserID\":105,\"Date\":\"2016-03-11T04:20:24.1854996+02:00\"}\n\n"
very clearly including the newlines.
This isn't an answer per say but hopefully it will lead one. I was able to get it working with the following code.
public void Ping(int id)
{
pings.Enqueue(new PingData { ID = id });
Response.ContentType = "text/plain";
Response.Write("id received");
}
public void Message()
{
int count = 0;
Response.ContentType = "text/event-stream";
do {
PingData nextPing;
if (pings.TryDequeue(out nextPing)) {
Response.ClearContent();
Response.Write("data:" + nextPing.ID.ToString() + " - " + nextPing.Date.ToLongTimeString() + "\n\n");
Response.Write("event:time" + "\n" + "data:" + DateTime.Now.ToLongTimeString() + "\n\n");
count = 0;
Response.Flush();
}
if (!Response.IsClientConnected){break;}
Thread.Sleep(1000);
count++;
} while (count < 30); //end after 30 seconds of no pings
}
The line of code that makes the difference is the second Response.Write. The message doesn't appear in the browser until the next ping similar to your issue, but the ping always appears. Without that line the ping will appear only after the next ping, or once my 30 second counter runs out.
The missing message appearing after the 30 second timer leads me to conclude that this is either a .Net issue, or there's something we're missing. It doesn't seem to be an event source issue because the message appears on a server event, and I've had no trouble doing SSE with PHP.
For reference, here's the JavaScript and HTML I used to test with.
<input type="text" id="pingid" placeholder="ID" /><br />
<input type="button" id="ping" value="Ping" />
<div id="timeresponse"></div>
<div id="pingresponse"></div>
<script>
var es = new EventSource('/Home/Message');
es.onmessage = function (e) {
console.log(e.data);
document.getElementById('pingresponse').innerHTML += e.data + " - onmessage<br/>";
};
es.addEventListener("ping", function (e) {
console.log(e.data);
document.getElementById('pingresponse').innerHTML += e.data + " - onping<br/>";
}, false);
es.addEventListener("time", function (e) {
document.getElementById('timeresponse').innerHTML = e.data;
}, false);
es.onerror = function () {
console.log(arguments);
console.log("event source closed");
es.close();
};
window.onload = function(){
document.getElementById('ping').onclick = function () {
var xmlhttp = new XMLHttpRequest();
xmlhttp.onload = function () {
console.log(this.responseText);
};
var url = '/Home/Ping?id=' + document.getElementById('pingid').value;
xmlhttp.open("GET", url);
xmlhttp.send();
};
};
</script>
Since an eventstream is just text data, missing the double line break before the first event is written to response could affect the client. The example from mdn docs suggests
header("Content-Type: text/event-stream\n\n");
Which could be applied apply to .NET response handling (note the side effects of Response.ClearContent()).
If it feels too hacky, you could start your stream with a keep-alive comment (if you want to avoid timing out you may have to send comments periodically):
: just a keep-alive comment followed by two line-breaks, Response.Write me first
I'm not sure if this will work because I can't try it now, but what about to add an End?:
Response.Flush();
Response.End();
The default behavior of .net is to serialize access to session state. It blocks parallel execution. Requests are processed sequentially and access to session state is exclusive for the session. You can override the default state per class.
[SessionState(SessionStateBehavior.Disabled)]
public class MyPulsingController
{
}
There is an illustration of this in the question here.
EDIT: Would you please try creating the object first and then passing it to Enqueue? As in:
PingData myData = new PingData { UserID = userID };
pings.Enqueue(myData);
There might be something strange going on where Dequeue thinks it's done the job but the the PingData object isn't properly constructed yet.
Also can we try console.log("I made it to the function") instead of console.log(e.data).
---- PREVIOUS INFORMATION REQUESTED BELOW ----
Please make sure that the server Debug.Print confirms this line of code:
Response.Write("data:" + JsonConvert.SerializeObject(nextPing, Formatting.None) + "\n\n");
Is actually executed? Please double check this. If you can capture the server sent response then can we see what it is?
Also could we see what browsers you've tested on? Not all browsers support server events.
I want get last transaction from blockchain.info On this site has API Websocket. But i can use API Websocket only for JavaScript. I can get last transaction using this code
<script type="text/javascript">
var conn = new WebSocket('ws://ws.blockchain.info/inv');
conn.onopen = function () {
console.log('open');
conn.send('{"op":"unconfirmed_sub"}');
}
conn.onclose = function () {
console.log('close');
}
conn.onerror = function (error) {
console.log('websocket error: ' + error);
}
conn.onmessage = function (e) {
console.log(e);
}
</script>
But i need get info above using php and after save in mysql. How can I get it through php?
Thank you in advance!
I found a library that solves the problem it phpws
Thanks to all.
I've tried to follow default Web API tutorial: http://www.asp.net/web-api/overview/getting-started-with-aspnet-web-api/tutorial-your-first-web-api
Here's what I did:
1) I added Action Routing in my WebApiConfig:
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
2) I added a link on my nav bar with client side javascript call:
<a onclick="RetrieveNext();" href="#">Retrieve next</a>
3) Here's my view:
<div class="row">
<div class="col-md-4">
<h2>Next barcode</h2>
<p id="barcode">
No available barcode
</p>
</div>
</div>
<script>
var uri = 'api/Barcode';
$(document).ready(function () {
});
function RetrieveNext() {
uri = 'api/Barcode/RetrieveNext';
$.getJSON(uri)
.done(function (data) {
$('#barcode').text(data);
})
.fail(function (jqXHR, textStatus, err) {
$('#barcode').text('Error: ' + err);
});
}
</script>
4) Here's my simple ApiController with 1 Action:
public class BarcodeController : ApiController
{
[HttpGet]
public IHttpActionResult RetrieveNext()
{
string barcode = "123456";
if (barcode == null)
{
return NotFound();
}
return Ok(barcode);
}
}
When I click my link I'm getting: Error: Not Found inside of my <p id="barcode">, which means JavaScript works, but Action wasn't called.
Here's Call details:
What I missed here? I put breakpoint in my Action and I can't reach this code...
How stupid is THAT??? I found what is the issue here: 404 error after adding Web API to an existing MVC Web Application
It's related to global.asax file. Even when you add WEB API to your project, visual studio opens readme.txt file for you with few tips how to add few lines of code to your global.asax file. THERE'S no single word about WHERE you should put your code (here it's really makes difference!)
So, copying from post I listed ablove:
While it doesn't work with:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
GlobalConfiguration.Configure(WebApiConfig.Register); //I AM THE 4th
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
It works with:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register); //I AM THE 2nd
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
I keep getting this error in my JavaScript no matter what fix I try. It's almost as if $.connection is not being recognized even though I have all the SignalR JavaScript libraries in place in my _layout. I get the following error in the Chrome browser console:Uncaught TypeError: "Cannot read property 'multipleFileHub' of undefined Index:508
(anonymous function) Index:508
x.event.dispatch jquery-2.0.2.js:4692
y.handle jquery-2.0.2.js:4376" of undefined".
Does it matter that my Global.asax inherits from "StsMvcHttpApplication" rather than the standard "System.Web.HttpApplication"? And in my case, I have to put the "RouteTable.Routes.MapHubs();" in my "RegisterRoutes" method rather than "Application_Start" since Application_Start doesn't fire fast enough... it starts hunting for controllers if I put it in the app start.
Would appreciate the help! I'll show the layout code first and then all the separate pieces of code:
_LAYOUT
#section head
{
#Scripts.Render("~/Scripts/Libs/jquery-2.0.2.min.js")
#Scripts.Render("~/Scripts/Libs/jquery-ui-1.10.3.min.js")
#Scripts.Render("~/Scripts/Libs/jquery.validate.min.js")
#Scripts.Render("~/Scripts/Libs/jquery.validate.unobtrusive.min.js")
#Scripts.Render("~/Scripts/Libs/modernizr-2.6.2.js")
#Scripts.Render("~/Scripts/Libs/modernizr.custom.blobconstructor.js")
#Scripts.Render("~/Scripts/SidebarMenu.js")
#Scripts.Render("~/Scripts/BC_Common.js")
#Scripts.Render("~/Scripts/scene.layoutservice.js")
#Scripts.Render("~/Scripts/scene.dataservice.js")
#Scripts.Render("~/Scripts/jquery.signalR-1.1.2.min.js")
#Scripts.Render("~/signalr/hubs")
#Scripts.Render("~/Scripts/scene.startup.js")
}
INDEX.CSHTML
$('#dBtn').click(function () {
var docIds = sceneLayoutService.getSelection();
if (docIds.length === 0) {
alert("you need to select one");
return false;
} else {
var docIdsParam = jQuery.param(docIds.map(function (value) {
return { "name": "docIds", "value": value };
}));
// Proxy created on the fly
var test_connection = $.connection.multipleFileHub;
// Start the connection
$.connection.hub.start().done(function() {
test_connection.server.send("test");
});
}
return true;
});
SERVER CODE:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
namespace Portal.Web.Hubs
{
[HubName("multipleFileHub")]
public class multipleFileHub : Hub
{
public void Send(string message)
{
// Call the addMessage method on all clients
Clients.All.addMessage(message);
}
}
}
GLOBAL.ASAX ROUTING
public static void RegisterRoutes(RouteCollection routes)
{
RouteTable.Routes.MapHubs();
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("{*favicon}", new { favicon = #"(.*/)?favicon.ico(/.*)?" });
routes.Ignore("{*allpng}", new { allpng = #".*\.png(/.*)?" });
routes.Ignore("{*allgif}", new { allgif = #".*\.gif(/.*)?" });
routes.Ignore("{*alljpg}", new { alljpg = #".*\.jpg(/.*)?" });
routes.MapRoute(
"Error", // Route name
"Error/{action}/{id}", // URL with parameters
new {controller = "Error", action = "Index", id = UrlParameter.Optional });
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Landing", id = UrlParameter.Optional } // Parameter defaults
);
}
ALL JAVASCRIPT REFERENCES ON THE PAGE
<script src="/ConnectPortal/Scripts/Libs/jquery-2.0.2.min.js"></script>
<script src="/ConnectPortal/Scripts/Libs/jquery-ui-1.10.3.min.js"></script>
<script src="/ConnectPortal/Scripts/Libs/jquery.validate.min.js"></script>
<script src="/ConnectPortal/Scripts/Libs/jquery.validate.unobtrusive.min.js"></script>
<script src="/ConnectPortal/Scripts/Libs/modernizr-2.6.2.js"></script>
<script src="/ConnectPortal/Scripts/Libs/modernizr.custom.blobconstructor.js"></script>
<script src="/ConnectPortal/Scripts/SidebarMenu.js"></script>
<script src="/ConnectPortal/Scripts/BC_Common.js"></script>
<script src="/ConnectPortal/Scripts/scene.layoutservice.js"></script>
<script src="/ConnectPortal/Scripts/scene.dataservice.js"></script>
<script src="/ConnectPortal/Scripts/jquery.signalR-1.1.2.min.js"></script>
<script src="/ConnectPortal/signalr/hubs"></script>
<script src="/ConnectPortal/Scripts/scene.startup.js"></script>
It turns out the cause of this issue was because the jquery library was being loaded on the page a second time. There was another javascript library being used in the layout that was inserting the non-minified jquery library on the page after the first minified one. It was hard to find this since the code to insert the other jquery library was not displayed on the layout page. Anyway, just thought I'd let all who read this know that the issue is DEFINITELY related to the jquery library being added after the signalR library.
David Fowler, from the above comments, was spot on! Thanks!