SignalR client function cant access to updated $scope variables - c#

I have making a web app using AngularJS, .NET 4.5 and SignalR.
One page in the app includes list of tasks to do, and I getting the list of the tasks from the server by AJAX and web method.
After it, I am registering to SignalR Hub witch the work of it is to update the tasks status.
I have angular service for connecting to SignalR and for the function on,off and invoke:
.factory('hubProxy', ['$rootScope',
function ($rootScope) {
function backendFactory(hubName) {
var connection = $.hubConnection("http://localhost:52158/");
var proxy = connection.createHubProxy(hubName);
proxy.on("dummy", function () { // do not remove - dummy is must here
});
connection.start().done(function () { });
return {
on: function (eventName, callback) {
proxy.on(eventName, function (a,b,c,d,e,f,g,h,i,j) {
$rootScope.$apply(function () {
if (callback) {
callback(a,b,c,d,e,f,g,h,i,j);
}
});
});
},
off: function (eventName, callback) {
proxy.off(eventName, function (result) {
$rootScope.$apply(function () {
if (callback) {
callback(result);
}
});
});
},
invoke: function (methodName, arg1, arg2, callback) {
proxy.invoke(methodName, arg1, arg2)
.done(function (result) {
$rootScope.$apply(function () {
if (callback) {
callback(result);
}
});
});
}
};
};
return backendFactory;
}]);
After it I am getting the task hub:
var tasksHub = hubProxy('taskHub');
Then to change task status I am using the following:
tasksHub.invoke("changeTaskStatus", task, status);
Until here everything works perfect.
The server hub function (changeTaskStatus) doing the following:
task.ChangeStatus((GeneralVars.PROJECT_STATUS)status);
Clients.All.taskStatusChanged(task.t_id,status);
So I am getting the task id and the status and want to change it on client side, I am getting it like this:
tasksHub.on("taskStatusChanged", function (t_id, status) {
var task = null;
for (var i = 0; i < $scope.levelTasks.length; i++) {
if ($scope.levelTasks[i].t_id == t_id) {
task = $scope.levelTasks[i];
break;
}
}
task.status = status;
$scope.$apply();
}
My problem is, that the client function in "taskStatusChanged" function, does not know the updated $scope variables.
The $scope.levelTasks is undefiend there even that I am getting it from server by ajax. And if I am moving the "taskStatusChanged" function to the ajax callback (where $scope.levelTasks is initialize) so the function will know the $scope.levelTasks but will register multiple times (every time I am getting the tasks from server)
Is there a way the client SignalR function will get access to the updated $scope varibles?

So what I did to solve it is to create a service for the tasks, then in the signalr function I am getting the tasks from the task service

Related

Am I making synchronous call for this service and using that service properly in controller?

My brother told me to write synchronous call for service, but the code below gives me an error. How can I do this correctly?
Here is my Angular service and Controller
app.service("StateService", function ($http, $q) {
var factory = $q.defer();
factory.getStateData = function () {
return $http.get("/State/GetStateData")
.then(function (response) {
factory.resolve(response.data);
return factory.promise;
}, function (response) {
factory.reject(response);
return factory.promise;
});
};
factory.getCountryById = function (Id) {
return $http.post('/Country/GetCountryById?Id=' + Id)
.then(function (response) {
factory.resolve(response.data);
return factory.promise;
}, function (response) {
factory.reject(response);
return factory.promise;
});
};
};
app.controller("statecontroller", function ($scope, StateService, $http, $timeout) {
// get function
$scope.GetStateData = function () {
StateService.getStateData().then(function (d) {
$scope.States = d.data;
$scope.fillAllData();
}, function (response) {
alert('error occurred' + response.data.ExceptionMesage);
});
};
$scope.fillAllData = function () {
for (var i = 0; i < $scope.States.length; i++) {
if ($scope.States[i].CountryId)
{
// var val = $scope.GetCountryById($scope.States[i].CountryId);
var cName = "";
var Id = $scope.States[i].CountryId;
StateService.getCountryById(Id).then(function (d) {
if (d.data)
cName = d.data.CountryName;
}, function (response) {
console.log('error occurred' + response.data.ExceptionMesage);
});
$scope.States[i].CountryName = cName;
}
else
$scope.States[i].CountryName = null;
}
}
};
Are you sure you want to make synchronous calls? Synchronous calls would halt the browser while waiting for the response and that's almost always never what you want. I reckon you actually mean asynchronous.
You may have misunderstood how services work. It's basically just a singleton object that gets injected by Angular. Additionally, the $http-service already creates promises for you so you could simplify your server drastically by writing it like this:
app.service("StateService", function ($http) {
var service = this;
service.getStateData = function () {
return $http.get("/State/GetStateData");
};
service.getCountryById = function (Id) {
return $http.post('/Country/GetCountryById?Id=' + Id);
};
};
This should work perfectly fine with your implementation in the controller.
Synchronous method - but, really, don't do this
app.service("StateService", function () {
var service = this;
service.getStateData = function () {
var req = new XMLHttpRequest();
req.open('GET', '/State/GetStateData', false); // false makes it synchronous
req.send(null);
if (req.status === 200) {
return req.responseText);
}
};
...
};
Then in your controller you consume it like this:
app.controller("statecontroller", function ($scope, StateService, $http, $timeout) {
// get function
$scope.GetStateData = function () {
$scope.States = StateService.getStateData();
$scope.fillAllData();
};
...
});
All left then is to answer angry support calls from your users whos browser you're blocking the execution from.
Also note that synchronous request may not work in all newer browsers:
Note: Starting with Gecko 30.0 (Firefox 30.0 / Thunderbird 30.0 /
SeaMonkey 2.27), synchronous requests on the main thread have been
deprecated due to the negative effects to the user experience.
From the documentation
Yet another reason not to do this.

signalR calls a server method and that method calls a callback method ,outside the hub. How can I call client function from that method?

I want to process a stream of data and need to display the processed data near real time. For that I created a hub class
public class AzureGuidanceEventHubReceiver : Hub
{
EventProcessorHost eventProcessorHost;
public async void ProcessEvents()
{
//Do some code here
eventProcessorHost.RegisterEventProcessorAsync<SimpleEventProcessor>();
}
}
And the class which processes the data is,
public class SimpleEventProcessor : IEventProcessor
{
public async Task ProcessEventsAsync(PartitionContext context, IEnumerable<EventData> events)
{
foreach (EventData eventData in events)
{
int data;
var newData = this.DeserializeEventData(eventData);
//how to display newData in the browser????????????????????????????????
}
}
My client side code is
<script type="text/javascript">
$(function () {
var receiverHub= $.connection.azureGuidanceEventHubReceiver;
receiverHub.client.displayMessage = function (data) {
var encodedData = $('<div />').text(data).html();
// Add the message to the page.
$('#discussion').append('<li><strong>' + encodedData + '</li>');
};
//// Start the connection.
$.connection.hub.start().done(function () {
$('#sendmessage').click(function () {
receiverHub.server.processEvents();
});
});
});
Here I made a call to ProcessEvents method in the Hub class and that registers the SimpleEventProcessor. And thus execution comes into ProcessEventsAsync in SimpleEventProcessor. From this ProcessEventsAsync method, I need to call the clientside code to display the data. Do I need to make SimpleEventProcessor also as a hub class?
You can use following code to get hold of HubContext which allows you to invoke client methods from outside of hub istance:
var hubContext = GlobalHost.ConnectionManager.GetHubContext<AzureGuidanceEventHubReceiver>();
hubContext.Clients.All.displayMessage(dataToDisplay);
Here I am facing a problem that The client method DisplayMessage is not executing everytime. I need to display a stream of message. But when I put debugger in the client code, it executes everytime.Here is my client code.
chat.client.displayMessage = function (data) {
// Html encode display data
debugger;
var encodedData = $('<div />').text(data.GPSPosition).html();
var data = encodedData.split("\n");
var varlat = data[0].replace("Latitude:","").trim();
var varlong = data[1].replace("Longitude:", "").trim();
ShowInGoogleMap(varlat, varlong);
};
how can i display a stream of messages? Why it is only working with debugger?

SignalR Return value to client only works some time

I have made a little "life" notification system, so when a user on my website recieves a new mail they get a notification life at the site.
It works but only some times, so i hope that someone can tell me what happens and how i can fix it.
When I debug and check totalNewMessages always has a value 0 or above but from what i can see, the problem is between the last line in NotificationHub and the jquery part.
If you look in the jquery Code, i added a Alert(totalNewMessages); But it dont always fire. What could be the problem?
NotificationHub.cs
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
return context.Clients.Client(Users.Values.FirstOrDefault(i => i.ProfileId == UserID).ConnectionIds).RecieveNotification(totalNewMessages);
Jquery:
<script type="text/javascript">
$(function () {
// Declare a proxy to reference the hub.
var hiddenID = $('#HiddenID');
var Check = $('#Check');
var notifications = $.connection.notificationHub;
// Create a function that the hub can call to broadcast messages.
notifications.client.recieveNotification = function (totalNewMessages) {
// Add the message to the page.
var notification = $('#lblNotificationNumber')
alert(totalNewMessages);
if (totalNewMessages > 0) {
notification.fadeIn('slow');
notification.text(totalNewMessages);
}
else {
notification.fadeOut('slow');
}
};
// Start the connection.
$.connection.hub.start().done(function () {
if (hiddenID.val() > 0) {
notifications.server.check(hiddenID.val());
notifications.server.sendNotifications(hiddenID.val());
}
}).fail(function (e) {
alert(e);
});
$('#btnLogout').click(function () {
$.removeCookie("test");
});
//$.connection.hub.start();
});
</script>

Can you specify context in signalr client events?

I'm attempting to use signalr for a board game app so that when a player moves a piece on one client, all clients can get updated. The problem I'm running into is not having a clear way to abstract the signalr parts of the app so that consumers of it don't need to worry about things like starting connections etc. I decided to skip sing the generated proxy and instead invoke things directly. The only remaining hurdle is that when the request is received by the client from the server, the this context in the event callback is of the hub, not of the owner of the callback. I'd like to know if there's a way to pass in a context.
Code:
Signalr service (like a base class to manage the connection and eventually events):
define('service.signalr', ['jquery'], function ($) {
var connection = $.hubConnection();
var start = function () {
connection.start();
};
return {connection: connection, start: start}
});
Service with specific functionality - in this case handling piece movement:
define('service.game', ['jquery', 'service.signalr'], function ($, signalr) {
var gameHubProxy = signalr.connection.createHubProxy('gameHub');
var addHandler = function (name, callback) {
gameHubProxy.on(name, callback);
}
var moveFigure = function (figureId, targetSpaceId) {
var deferred = $.Deferred();
gameHubProxy.invoke('moveFigure', figureId, targetSpaceId).done(function () { deferred.resolve(); });
return deferred.promise();
};
return { moveFigure: moveFigure, addHandler: addHandler }
});
Calling the server method on the service (the event trigger is for the client performing the action so it doesn't process twice):
define('model.space', ['backbone', 'service.game'], function (Backbone, gameService) {
var Space = Backbone.Model.extend({
defaults: { id: 0, figure: null },
moveFigure: function (figureId) {
var self = this;
var spaceId = self.get('id');
gameService.moveFigure(figureId, spaceId).done(function () {
self.trigger('figureMoved', figureId, spaceId, false);
});
}
});
return Space;
});
And trying to listen to the server's response:
define('view.board', ['jquery', 'underscore', 'backbone', 'helpers', 'bootstrapData', 'service.game', 'aggregate.space'], function ($, _, Backbone, helpers, bootstrapData, gameService, Space) {
var Board = Backbone.View.extend({
initialize: function () {
this.spaces = new Space.Collection(bootstrapData.spaces);
this.listenTo(this.spaces, 'figureMoved', this.updateFigurePosition);
gameService.addHandler('figureMoved', this.updateFigurePosition);
},
updateFigurePosition: function (figureId, spaceId, drawFigure) {
var figure = null;
var oldSpace = this.spaces.find(function (space) {
figure = space.get('figure');
return figure && figure.id == figureId;
});
//code continues, but has already failed on this.spaces
},
});
return Board;
});
The obvious answer here after some sleep is to wire up the callback to a function in the module whose context I want that is just a passthrough to the actual method I want to call, so that this is then set appropriately. Such as:
initialize: function () {
var self = this;
self.spaces = new Space.Collection(bootstrapData.spaces);
self.listenTo(self.spaces, 'figureMoved', self.updateFigurePosition);
gameService.addHandler('figureMoved', function (figureId, spaceId, drawFigure) {
self.updateFigurePosition(figureId, spaceId, drawFigure);
});
},
I still wonder if this can be a viable solution in all cases. Seems odd that I have to wrap my function in another function just so I can reference the variables I want. But I suspect this is less a problem with signalr, backbone, or require and more with a C# guy that's late to the Javascript party and still stumbles on some of the language's "features".

Updating page content using JS during asynchronous operation

I am trying to solve an issue with webservice call in my MVC 4 project. What I am doing is that when user submits form on page, I call webservice and wait for result. Meanwhile server returns callbacks (it is returning strings containing finished steps of work on server side). What I would like to do is to display contents of "log" I am building from callback on the page and refresh it repeatedly to display the progress.
The issue I run into is, that either I will have asynchronous call of webservice in my controller in which case I am not waiting for result from webservice and that means that user will not stay on the page that should be displaying progress, or I will call it synchronously, in which case javascript on the page will not get response from controller until the final response arrives (so no updates are displayed).
My desired flow is:
User submits the form
Server is called in controller
Server sends callbacks, controller processess them by expanding "log" variable with whatever arrives
User still sees the same page, where "log" (contained in specific div) is being periodically refreshed by javascript while controller waits for final result from server
Server returns final result
Controller finishes its code and returns new view
This is my post method, which currently doesnt wait for the response and proceeds immediately further:
[HttpPost]
public async Task<ActionResult> SubmitDetails
(DocuLiveInstallationRequest submittedRequest, string command)
{
request = submittedRequest;
try
{
switch (command)
{
case "Test":
{
request.OnlyTest = true;
DocuLiveInstallationStatus installStatus
= await IsValidStatus();
if (installStatus == null)
{
ViewBag.Fail = Resources.AppStart.TestNoResult;
return View("SubmitDetails", request); ;
}
else
{
status = installStatus;
if (status.Result)
{
ViewBag.Success = Resources.AppStart.TestSucces;
ViewBag.Log = installLog;
}
TempData["installationStatus"] = installStatus;
return View("SubmitDetails", request);
}
}
case "Submit":
{
request.OnlyTest = false;
DocuLiveInstallationStatus installStatus = await Install();
if (installStatus == null)
{
ViewBag.Fail = Resources.AppStart.InstallationNoResult;
return View("SubmitDetails", request); ;
}
else
{
status = installStatus;
TempData["installationStatus"] = installStatus;
TempData["installLog"] = installLog;
return RedirectToAction("Login",controllerName:"Login");
}
}
}
ViewBag.TestFail = Resources.AppStart.SubmitFailure;
return View("SubmitDetails", request); ;
}
catch
{
return View();
}
}
this is javascript I prepared for the view:
<script type="text/javascript">
$(document).ready(function () {
//$("#submitDetails").click(function () {
var progress = 0;
//$("#submitDetails").attr('disabled', 'disabled');
var statusUpdate = setInterval(function () {
$.ajax({
type: 'GET',
url: "/AppStart/GetInstallProgress",
datatype: "application/html; charset=utf-8",
success: function (data) {
if (data && data != "") {
$("div.status-message").text(progress);
}
}
});
}, 2000);
//});
});
</script>
Currently I just display the log on the next page (at this stage of development server returns response very swiftly), but I need to call the server, display progress from callbacks while waiting for result and THEN navigate to another page (depending on the result). I feel like I am very close, but I can't get it working.
PS: I am open to other solutions than updating page content. I don't really mind how the goal will be accomplished, but updating the page is preferred by the customer.

Categories