Why SignalR change function hitting many many times? - c#

I try to develop an asp.net mvc application and also trying to use signalr. The problem is that i have two tables that control user notificitaions in project. I have a Notification table and also NotificationUser table which is many to many table of notification and user tables. I am trying achive that if a user create a notification to another user in system I try to show a pop-up that confirm user with a simple message like 'Hey!New notification received'. The problem is javascript change function of signalr hitting so many times.All step i used in signalR listed below
the stored procedure
ALTER PROCEDURE [dbo].[GetNotifications]
#userid int
AS
BEGIN
select n.Ntf_Title,Ntf_Description,n.Ntf_Date from dbo.SysNotifications n INNER JOIN dbo.SysNotificationUser u on n.Ntf_ID =u.NtU_NtfID where NtU_UserID=#userid AND NtU_IsRead=0
END
The Hub
[HubName("signalRHub")]
public class NtfHub : Hub
{
[HubMethodName("notifyChanges")]
public static void NotifyChanges()
{
var context = GlobalHost.ConnectionManager.GetHubContext<NtfHub>();
context.Clients.All.notifyChanges();
}
}
The StartUp Class
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR();
}
}
The Partial View
[HttpGet]
public ActionResult GetNtf()
{
//NtfRepo rp = new NtfRepo(this.HttpContext);
string connectionString = ConfigurationManager.ConnectionStrings["conn"].ConnectionString;
int userid =id;
using (SqlConnection sqlcon = new SqlConnection(connectionString))
{
using (SqlCommand sqlcom = new SqlCommand("[GetNotifications]", sqlcon))
{
sqlcon.Open();
sqlcom.CommandType = CommandType.StoredProcedure;
sqlcom.Parameters.AddWithValue("#userid", userid);
sqlcom.Notification = null;
SqlDependency dependancy = new SqlDependency(sqlcom);
dependancy.OnChange += dependancy_OnChange;
var reader = sqlcom.ExecuteReader();
var ntf= reader.Cast<IDataRecord>()
.Select(e => new PopulateNtfBar()
{
Title = e.GetString(0),
Description = e.GetString(1),
TimeDiff = FindDifferenceTime(e.GetDateTime(2))
}).ToList();
return PartialView("~/Views/Shared/CheckNotification.cshtml", ntf);
}
}
}
At Last,The Script
$(function () {
var notification = $.connection.signalRHub;
// Create a function that the hub can call to broadcast messages.
notification.client.notifyChanges = function () {
getData();
toastr.warning("Hey,You have Ntf");
};
// Start the connection.
$.connection.hub.start().done(function () {
getData();
}).fail(function (e) {
});
});
function getData() {
var tbl = $("#header_notification_bar")
$.ajax({
url: '#Url.Action("GetNtf","Home")',
contentType: 'application/html ; charset:utf-8',
type: 'GET',
dataType: 'html'
}).success(function (result) {
tbl.empty().append(result);
}).error(function () {
});
}
notification.client.notifyChanges hitting so many times if a user create a notification.Where is the problem? Any idea? i cannot optimize it
EDIT 1
I am calling NtfHub.NotifyChanges in controller.
void dependancy_OnChange(object sender, SqlNotificationEventArgs e)
{
if (e.Type == SqlNotificationType.Change)
{
NtfHub.NotifyChanges();
}
}

Although I think that SqlDependency is wrong approach for this feature, you could try to solve this specific problem this way:
Add parameter "subscribeToNotifications" to your controller action
public ActionResult GetNtf(bool subscribeToNotifications)
Create SqlDependency only if it is True.
Then subscribe to notifications only on hub started (this will prevent creation of multiple SqlDependencies for same user):
$(function () {
var notification = $.connection.signalRHub;
// Create a function that the hub can call to broadcast messages.
notification.client.notifyChanges = function () {
getData(false);
toastr.warning("Hey,You have Ntf");
};
// Start the connection.
$.connection.hub.start().done(function () {
getData(true);
}).fail(function (e) {
});
});
function getData(subscribeToNotifications) {
var tbl = $("#header_notification_bar")
$.ajax({
url: '#Url.Action("GetNtf","Home")' + '?subscribeToNotifications=' + subscribeToNotifications,
contentType: 'application/html ; charset:utf-8',
type: 'GET',
dataType: 'html'
}).success(function (result) {
tbl.empty().append(result);
}).error(function () {
});
}
But be aware that every page refresh will still create new listener without managing subscriptions on server side.
Option 2 is to create single SqlDependency (on server app start) omitting userId parameter - anyway you are sending notification to all users no matter which one got the message.
Option 3 - the real solution is get rid of SqlDependency at all and send notification only to specific user (recipient of the message)

The reason is that you are not unsubscribing from the dependancy_OnChange event, a sqldependency trigger is a one shot execution, so you have to subscribe to the new one every time it fires, what you are not doing is unsubscribing from the previous event handler, so when you subscribe to the new one, you now have multiple handlers for the same trigger.
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();
}

Related

SqlDependency OnChange event firing multiple times

I'm have a comment system on a project where you can view different pages and they have their own comments. I'm trying to use signalR with SqlDependency to automatically pull new comments on the page.
My problem is that if multiple people have a connection open with SqlDependency the number of "onchange" events called from the sqlnotification start getting multiplied. Instead of the onchange even getting called once it will be called multiple times for each user. (Even if they are not viewing the same comments page)
I've pretty much exhausted all searching here with the most common response being that I need to unsubscribe the event when it's called like this:
private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
SqlDependency dependency = (SqlDependency)sender;
dependency.OnChange -= new OnChangeEventHandler(dependency_OnChange);
if (e.Info.ToString().ToLower().Trim() == "insert")
{
MyHub.Show();
}
}
This didn't seem to change anything for me so I'm lost on what the best way to prevent this would be.
GetData method:
[WebMethod]
public IEnumerable<DUpdates> GetData(string dnum, string date)
{
if (Common.IsValidInt32(dnum)) //just a function that check for valid int
{
using (var connection =
new SqlConnection(ConfigurationManager.ConnectionStrings["SConnectionString"].ConnectionString))
{
connection.Open();
using (SqlCommand command = new SqlCommand(#"SELECT [Comment] FROM dbo.DUpdates WHERE (DID = " + dnum + ") AND (Deleted = 0) AND CreateDate > #Date ORDER BY CreateDate DESC", connection))
{
command.Parameters.Add("#Date", SqlDbType.DateTime);
command.Parameters["#Date"].Value = date;
command.Notification = null;
SqlDependency dependency = new SqlDependency(command);
dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
if (connection.State == ConnectionState.Closed)
connection.Open();
using (var reader = command.ExecuteReader())
{
return reader.Cast<IDataRecord>().Select(x => new DUpdates()
{
comment = x.GetString(0)
}).ToList();
}
}
}
}
JS stuff:
$(function() {
var job = $.connection.myHub;
job.client.displayStatus = function () {
getData();
};
$.connection.hub.start();
getData();
});
function getData() {
var params = (new URL(document.location)).searchParams;
var id = params.get("id");
var dt = $("#accessdate").val();
$.ajax({
url: 'dupdatesservice.asmx/GetData',
data: JSON.stringify({dnum: id, date: dt}),
contentType: "application/json; charset=utf-8",
dataType: "json",
type: "POST",
success: function (data) {
if (data.d.length > 0) {
$("#testdiv").prepend(data.d[0].comment);
}
}
});
}
Can provide other code if needed.
The issue here is that I was creating a new SqlDependency for each user that was on a page (or same user with multiple windows). So if 2 windows were open for a page, then it would check for notifications twice and send the response twice if there was something new. Because of the ajax request, now all of those SqlDependencies were doubled so I'd get 4 responses the next time, then 8 and so on.
What I decided to do instead was to essentially change my pages to private chats using signalr and just ditched the SqlDependency stuff. So now if a user goes to one page, they are connected with anyone else on the page and anytime someone submits a "comment" it also gets sent to other people viewing the page.

signalr client doesn't fire on click listener

The process is : When the .post-thread is clicked, it calls the server through the hub, returns a <p data-id = id class = 'take-thread'>, and appends on foo div. Then I click the <p>, it should run TakeThread at server side.
However, when I click on the newly appended <p>, console.log('test') doesn't fire until I added the code in the star rectangle. I don't understand why. There is already a listener in the Hub.start().done() to trigger the click. Why do I have to do it in the hub client function ?
JS:
var chat = $.connection.chatHub;
chat.client.updateThings(id){
$('.foo').append('<p data-id = "id" class = "take-thread"></p>');
// Why do I need to these code below?
************************************************
* $('.take-thread').on('click', function () { *
* console.log("test"); *
* chat.server.takeThread("blah..."); *
* }); *
************************************************
}
$.connection.hub.start().done(function () {
$('.post-thread').on('click', function () {
chat.server.postThread("Test"); // works
});
$('.take-thread').on('click', function () {
console.log("test");
chat.server.takeThread("blah...");
});
}
C# Hub:
public void PostThread(string title) {
var id = someCalculation();
Clients.All.updateThings(id);
}
public void TakeThread(string title) {
// do things
}
This is a binding problem. Dynamic elements added to the page after document.ready is called do not automatically get rebound. This is what you were doing when you added the second on click event to the updateThings function.
You need to us .on() with a static parentSelector that is there on document.ready (.foo) together with a selector of your dynamic element being added by the SignalR callback (.take-thread).
See this example using standard alerts in place of SignalR: http://jsfiddle.net/kspearrin/0zyoqyxL/
In the end, your Javascript should be updated to the following:
var chat = $.connection.chatHub;
chat.client.updateThings(id){
$('.foo').append('<p data-id="id" class="take-thread"></p>');
}
$.connection.hub.start().done(function () {
$('.post-thread').on('click', function () {
chat.server.postThread("Test"); // works
});
$('.foo').on('click', '.take-thread', function () {
console.log("test");
chat.server.takeThread("blah...");
});
}

How to set the sender when using AJAX to call MVC5 method

I have been stuck on this for a few weeks, any help is very much appreciated!
I am building an MVC5 web application (This is my first C# & ASP.NET project). The model of this application is a web service. There is a page with a checkbox, and when clicked, this calls a bit of jQuery that uses AJAX to call a method in one of my controllers. This method calls a web service and updates a boolean value. This all seems to be working... my issue is that I need checkbox to be sent with the AJAX call, so that I can update a label on the page associated with the checkbox.
Is there a better way to accomplish this? (It seems rather hack-ish to me, using javascript to call my code). My question, though, is this: How can I pass the sender with an AJAX call?
CSHTML Page:
#Html.CheckBox("checkbox_subscribe", new{#id = "subscribeBox"})
#Html.Label("subscribebox", "Please notify me via email of any changes in lead times.")
<script>
$(document).ready(function () {
$("#subscribeBox").change(function (event) {
$.ajax({
type: "POST",
url: "#Url.Action("SubscribeClick", "Home")",
success: function (result) {
alert(result);
}
});
});
});
</script>
Controller Method:
public string SubscribeClick(object sender, EventArgs e)
{
String flag;
if (sender == checked)
{
flag = "Y";
}
else
{
flag = "N";
}
websecurity.n_securitySoapClient proxy = new websecurity.n_securitySoapClient();
String result = proxy.setsubscribeflag("11", flag, "leadtimes");
if (result.StartsWith("<success>"))
{
if (flag == "Y") result = "Successfully subscribed for email.";
else result = "Successfully unsubscribed from email.";
}
return result;
}
Just a side note: When I try casting the sender object to a CheckBox object type, I get the error: "InvalidCastException was unhandled by use code"
Ajax is client side code and has no concept of c# code such as object sender, EventArgs e. Change you method to accept a boolean
[HttpPost]
public ActionResult SubscribeClick(bool isChecked)
{
if(isChecked)
{
....
}
else
{
....
}
return Json(result,
}
and then in the script, pass true or false based on the state of the checkbox
$("#subscribeBox").change(function (event) {
var isChecked = $(this).is(':checked');
$.ajax({
type: "POST",
url: "#Url.Action("SubscribeClick", "Home")",
data: { isChecked: isChecked },
success: function (result) {
alert(result);
}
});
});

Database Change Notifications in ASP.NET using SignalR and SqlDependency

I'm a serious MVC & SignalR newbie!
I found this tutorial online that shows how to use Database Change notifications and display the data in an MVC app. The issue I'm having is replicating the MVC side of things. I've managed I think to work through the tutorial correctly and spin up the application, however, I'm getting an Undefined connection and the app bombs out.
Does anyone know where I might find the associated source files for this example, or has anyone managed to successfully implement this and can shed some light on the configuration of this from an MVC point of view.
Thanks!
To display real time updates from the SQL Server by using SignalR and SQL Dependency I've done these steps:
Step 1: Enable Service Broker on the database
The following is the query that need to enable the service broker
ALTER DATABASE BlogDemos SET ENABLE_BROKER WITH ROLLBACK IMMEDIATE ;
Step 2: Enable SQL Dependency
//Start SqlDependency with application initialization
SqlDependency.Start(connString);
Step 3: Create the hub Class
public class MessagesHub : Hub
{
private static string conString = ConfigurationManager.ConnectionStrings["DefaultConnection"].ToString();
public void Hello()
{
Clients.All.hello();
}
[HubMethodName("sendMessages")]
public static void SendMessages()
{
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<MessagesHub>();
context.Clients.All.updateMessages();
}
}
Step 4: Get the Data from the Repository
Create MessagesRepository to get the messages from the database when data is updated.
public class MessagesRepository
{
readonly string _connString = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;
public IEnumerable<Messages> GetAllMessages()
{
var messages = new List<Messages>();
using (var connection = new SqlConnection(_connString))
{
connection.Open();
using (var command = new SqlCommand(#"SELECT [MessageID], [Message], [EmptyMessage], [Date] FROM [dbo].[Messages]", connection))
{
command.Notification = null;
var dependency = new SqlDependency(command);
dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
if (connection.State == ConnectionState.Closed)
connection.Open();
var reader = command.ExecuteReader();
while (reader.Read())
{
messages.Add(item: new Messages { MessageID = (int)reader["MessageID"], Message = (string)reader["Message"], EmptyMessage = reader["EmptyMessage"] != DBNull.Value ? (string) reader["EmptyMessage"] : "", MessageDate = Convert.ToDateTime(reader["Date"]) });
}
}
}
return messages;
}
private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
if (e.Type == SqlNotificationType.Change)
{
MessagesHub.SendMessages();
}
}
}
Step 5: Register SignalR at startup class
app.MapSignalR();
Step 6: then use the method to show real time at your view
<script src="/Scripts/jquery.signalR-2.1.1.js"></script>
<!--Reference the autogenerated SignalR hub script. -->
<script src="/signalr/hubs"></script>
<script type="text/javascript">
$(function () {
// Declare a proxy to reference the hub.
var notifications = $.connection.messagesHub;
//debugger;
// Create a function that the hub can call to broadcast messages.
notifications.client.updateMessages = function () {
getAllMessages()
};
// Start the connection.
$.connection.hub.start().done(function () {
alert("connection started")
getAllMessages();
}).fail(function (e) {
alert(e);
});
});
function getAllMessages()
{
var tbl = $('#messagesTable');
$.ajax({
url: '/home/GetMessages',
contentType: 'application/html ; charset:utf-8',
type: 'GET',
dataType: 'html'
}).success(function (result) {
tbl.empty().append(result);
}).error(function () {
});
}
</script>
Hope this helps :)
You should provide your code, it's easier to figure out what the problem is that way.
From what you have mentioned, I can think of only two things.
1) The tutorial you used, it's using SignalR 1.0. If you are using SignalR 2.0, you should not follow the tutorial exactly.
A few things changed in SignalR 2.0, you can read about it using below link:
http://www.asp.net/signalr/overview/signalr-20/getting-started-with-signalr-20/upgrading-signalr-1x-projects-to-20
2) If you are using SignalR 1.0, make sure you include the "jquery" file correctly, and if it's correct, try to change the hub proxy path like below:
<script src="/signalr/Hubs"></script>
Hope it helps

Javascript context menu to C#

I have created a javascript context menu using Jquery that works perfectly. But there are two options. The first one is to create this context menu in C# (If that's possible). The second way is to run a C# Function when a button in the menu is clicked. Which option is the best and how do i start? Kind regards
Javascript:
function Menu($div){
var that = this,
ts = null;
this.$div = $div;
this.items = [];
// create an item using a new closure
this.create = function(item){
var $item = $('<div class="item '+item.cl+'">'+item.label+'</div>');
$item
// bind click on item
.click(function(){
if (typeof(item.fnc) === 'function'){
item.fnc.apply($(this), []);
}
})
// manage mouse over coloration
.hover(
function(){$(this).addClass('hover');},
function(){$(this).removeClass('hover');}
);
return $item;
};
this.clearTs = function(){
if (ts){
clearTimeout(ts);
ts = null;
}
};
this.initTs = function(t){
ts = setTimeout(function(){that.close()}, t);
};
}
// add item
Menu.prototype.add = function(label, cl, fnc){
this.items.push({
label:label,
fnc:fnc,
cl:cl
});
}
// close previous and open a new menu
Menu.prototype.open = function(event){
this.close();
var k,
that = this,
offset = {
x:0,
y:0
},
$menu = $('<div id="menu"></div>');
// add items in menu
for(k in this.items){
$menu.append(this.create(this.items[k]));
}
// manage auto-close menu on mouse hover / out
$menu.hover(
function(){that.clearTs();},
function(){that.initTs(3000);}
);
// change the offset to get the menu visible (#menu width & height must be defined in CSS to use this simple code)
if ( event.pixel.y + $menu.height() > this.$div.height()){
offset.y = -$menu.height();
}
if ( event.pixel.x + $menu.width() > this.$div.width()){
offset.x = -$menu.width();
}
// use menu as overlay
this.$div.gmap3({
action:'addOverlay',
latLng: event.latLng,
content: $menu,
offset: offset
});
// start auto-close
this.initTs(5000);
}
// close the menu
Menu.prototype.close = function(){
this.clearTs();
this.$div.gmap3({action:'clear', name:'overlay'});
}
Well you could create a server control in C# and emit the menu from it, but since you already have a working menu it's just easier to call a server-side method in response to a click. If you're using jQuery it's as easy as:
$.ajax({
url: "/Path/To/MyMethod",
type: "POST",
dataType: "JSON",
data: <some POST data>,
contentType: "application/json; charset=utf-8",
success: function (result) {
// Do your stuff here
},
error: function (jqXHR, textStatus, errorThrown) {
// Report error
}
});
The implementation of the server-side part can be either a static [WebMethod] in an ASPX page, or if you're using MVC then it can be a direct call to a controller method.
I am assuming what you are trying to do is call a c# method when an Item on the context menu is selected. If you are using an MVC model this is pretty easy to do. Use a call as follows passing the parameters in JSON format. I am just using a skeleton method from my code as an example you would call LibraryRead method when you click on the Context Menu Link
Client Side
function LibraryRead() {
$.ajax({
url : 'Library/ReadLibrary',
type : "POST",
data : JSON.stringify(idLibrary),
dataType : "json",
contentType : "application/json; charset=utf-8",
success : function(result) {
$(result).each(function() {
....Process Results
});
},
error : function() {
.....If something goes wrong, if you use a try catch in your code
which does not handle the exception correctly and something goes wrong
this will not be triggered. Use propper exception handling.
}
});
}
Server Side
// Post: /Library/ReadLibrary
[HttpPost]
public JsonResult ReadLibrary(int idLibrary)
{
try
{
var library = READ your library here from your data source
return this.Json(library);
}
else
{
return null;
}
}
catch (Exception ex)
{
//Exception handling omitted for simplicity
}
}
Do a search on google for MVC3 and JQuery / Javascript calls with JSON, there are loads of resources available.
If you are not using MVC pattern you may be able to use a web service or method in the code behind. You need to add the appropriate attribute over the method though like [Ajax.AjaxMethod()] or [WebMethod]

Categories

Resources