signalr avoid login repeat on load client side - asp.net-mvc-4

I am using signalR pushnotification service.
I have created a partial view. Inside partial view. Here is my client side code:
<script type="text/javascript">
var objHub;
$(function () {
objHub = $.connection.AnilHub;
loadClientMethods(objHub);
$.connection.hub.start()
.done(function () { objHub.server.connect();
console.log('Now connected, connection ID=' + $.connection.hub.id); }
// at the same time i want to insert into database to set user is online.
objHub.server.login('user1');
)
.fail(function () { console.log('Could not Connect!'); });
function loadClientMethods(objHub) {
objHub.client.getMessages = function (message) {
$('#divMessage').append('<div><p>' + message + '</p></div>');
var height = $('#divMessage')[0].scrollHeight;
$('#divMessage').scrollTop(height);
}
}
</script>
Hub Code
[HubName("MyHub")]
public class MainHub : Hub
{
public void Connect()
{
try
{
string userGroup = "test";
var id = Context.ConnectionId;
Groups.Add(id, userGroup);
Clients.Caller.onConnected(id, userGroup);
}
catch
{
Clients.Caller.NoExistAdmin();
}
}
public void NotifyAllClients(string Message)
{
Clients.Group("test").getMessages(Message);
}
public override Task OnConnected()
{
// Set status online on database
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled = false)
{
// set status disconnct in database
return base.OnDisconnected(stopCalled);
}
}
}
Now I just want to avoid re-loading check of login. because everytime I refresh the page it will call the connect method and call the hub method. How to avoid the re-connect issue. How Do I persist the things, even hub is not handle sessions.
Please suggest...

inside your html, in first load, create a random id and store it in cookies.
In your hub code, create an arraylist and store these random ids with corresponding connection ID.
In your html, try to read the random id from the cookies during each page refresh, if it is not found, it's a new connection, if it is found, use the old random id with a new connection ID to connect to your hub. Then in your hub arraylist, for this particular random id, replace the old connection ID with the new connection ID.

Related

Send a message to only one user with signalR

I am writing a chat application. I use Angular 6 and .Net Core 3.1.
I have read a lot of documents but still do not understand where there is an error.
My Hub Class
public class ChatHub : Hub
{
private static List<ConnectedUser> _connectedUsers = new List<ConnectedUser>();
public override Task OnConnectedAsync()
{
var username = Context.GetHttpContext().Request.Query["username"];
var status = _connectedUsers.FirstOrDefault(x => x.Username == username);
if (status == null)
{
_connectedUsers.Add(new ConnectedUser
{
ConnId = Context.ConnectionId,
Username = username
});
}
return base.OnConnectedAsync();
}
...
I have a list that keeps the connection id information of the users connected in my Hub Class. When users connect, I add to this list.
I operate in my Private Chat Function below.
public void PrivateChat(string toUser,string fromUser,string message)
{
_connectedUsers.ForEach(val =>
{
if(val.Username == toUser)
Clients.User(val.ConnId).SendAsync("receiveMessage", message,fromUser);
});
}
However, I cannot return to the user I want.
You use it in Angular this way
this._hubConnection.on('receiveMessage', (message,fromUser) => {
console.log(message , "+" , fromUser);
}
);
You should use
Clients.Clients(val.ConnId).SendAsync("receiveMessage", message,fromUser);

How to send Message to User id Saved in AspNetUsers Table Signal R

I am trying to send a message. I have tried to connection id
public Task SendMessaageToConnectionID(string ConnectionID,string Message)
{
return Clients.Clients(ConnectionID).SendAsync("RecieveMessage", Message);
}
it is successfully done
Now I am trying this
public Task SendMessageToUser(string userId,string Message)
{
return Clients.Clients(userId).SendAsync(Message);
}
I am sending the user id of user Saved in AspNetUser Table
How Can I send this to a User ID or is there any other way except connection id to send the message to user?
SignalR won't store the UserId-ConnectionId mappings for us. We need to do that by our own. For example, when some user sets up a connection to the Hub, it should trigger a ReJoinGroup() method.
In addition, in order to make sure the Groups property works fine, you need also :
invoke RemoveFromGroupAsync to remove the old <connectionId, groupName> mapping
invoke AddToGroupAsync to add a new <connectionId, groupName> mapping.
Typically, you might want to store these information in Redis or RDBMS. For testing purpose, I create a demo that stores these mappings in memory for your reference:
public class MyHub:Hub
{
/// a in-memory store that stores the <userId, connectionId> mappings
private Dictionary<string, string> _userConn = new Dictionary<string,string>();
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
public override async Task OnConnectedAsync()
{
// get the real group Name by userId,
// for testing purpose, I use the userId as the groupName,
// in your scenario, you could use the ChatRoom Id
var groupName = Context.UserIdentifier;
await this.ReJoinGroup(groupName);
}
// whenever a connection is setup, invoke this Hub method to update the store
public async Task<KeyValuePair<string,string>> ReJoinGroup(string groupName)
{
var newConnectionId = Context.ConnectionId;
var userId = Context.UserIdentifier;
await this._semaphore.WaitAsync();
try{
if(_userConn.TryGetValue(userId, out var oldConnectionId))
{
_userConn[userId]= newConnectionId;
// remove the old connectionId from the Group
if(!string.IsNullOrEmpty(groupName)){
await Groups.RemoveFromGroupAsync(oldConnectionId, groupName);
await Groups.AddToGroupAsync(newConnectionId, groupName);
}
} else {
_userConn[userId]= newConnectionId;
if(!string.IsNullOrEmpty(groupName)){
await Groups.AddToGroupAsync(newConnectionId, groupName);
}
}
} finally{
this._semaphore.Release();
}
return new KeyValuePair<string,string>(userId, newConnectionId);
}
/// your SendMessageToUser() method
public async Task SendMessageToUser(string userId,string Message)
{
// get the connectionId of target user
var userConn = await this.GetUserConnection(userId);
if( userConn.Equals(default(KeyValuePair<string,string>))) {
throw new Exception($"unknown user connection with userId={userId}");
}
await Clients.Clients(userConn.Value).SendAsync(Message);
}
/// a private helper that returns a pair of <UserId,ConnectionId>
private async Task<KeyValuePair<string,string>> GetUserConnection(string userId)
{
KeyValuePair<string,string> kvp = default;
string newConnectionId = default;
await this._semaphore.WaitAsync();
try{
if(this._userConn.TryGetValue(userId, out newConnectionId)){
kvp= new KeyValuePair<string, string>(userId, newConnectionId);
}
} finally{
this._semaphore.Release();
}
return kvp;
}
}

How to sync in memory data with disk in ASP NET

In a ASP NET Controller i have a service that returns a list of items.This service serves from the RAM the list to requesters.
The list can also be altered by a special group of users , so everytime it is altered i write the changes to disk and update my RAM from disk. (Reading my own writes this way)
From a JS client when i alter this list , the changes are written correctly on the disk , but when i forward a second request to get my list , i am served a stale list.I need to hit F5 for the client to get the right data.
I do not understand how does the RAM cache lags behind.
You can see in my service below that i have guarded the altering method with a lock.I have also tried without it to no avail.
Service
public class FileService : IADReadWrite {
private const int SIZE = 5;
private const string COMPUTER_FILE = #"computers.txt";
private List<Computer> computers = new List<Computer>();
private readonly object #filelock = new object();
private readonly Computer[] DEFAULT_COMPUTERS_LIST = new Computer[] {
new Computer(id:"W-CZC81371RS",Username:"A"),
new Computer(id:"W-CZC81371RQ",Username:"B"),
};
async Task<Computers> GetComputersAsymc() {
if (this.computers.Count == 0) {
var query = await Fetch();
this.computers = query.ToList();
}
var result = new Computers(this.computers);
return result;
}
public async Task<bool> AddComputerAsync(Computer computer) {
lock (filelock) {
if (this.computers.Any(x => x == computer)) {
return false;
}
this.computers.Add(computer);
File.WriteAllText(COMPUTER_FILE, JsonConvert.SerializeObject(this.computers, Formatting.Indented));
this.computers = JsonConvert.DeserializeObject<List<Computer>>(File.ReadAllText(COMPUTER_FILE));
}
return true;
}
---------------------Helpers --------------------------
private async Task<IEnumerable<Computer>> Fetch() {
if (!File.Exists(COMPUTER_FILE)) {
WriteComputersToDisk();
}
using (FileStream stream = new FileStream(COMPUTER_FILE, FileMode.Open, FileAccess.Read)) {
var raw = await File.ReadAllTextAsync(COMPUTER_FILE);
var comp = JsonConvert.DeserializeObject<List<Computer>>(raw);
return comp;
}
}
private void WriteComputersToDisk() {
var comps = DEFAULT_COMPUTERS_LIST;
var data = JsonConvert.SerializeObject(comps, Formatting.Indented);
File.WriteAllText(COMPUTER_FILE, data);
}
}
Controller
public class MyController:Controller
{
MyController(IADReadWrite service)
{
this.service=service;
}
IADReadWrite service;
[HttpGet]
public async Task<List<Computer>> GetAll()
{
return await service.GetComputersAsync();
}
[HttpPost]
public async Task AddComputer(Computer computer)
{
await service.AddComputerAsync(computer);
}
}
Scenario
Initial list : [0,1]
Client hits controller calling `AddComputer` {2}
I check the file , list is now: [0,1,2]
Client hits controller calling `GetComputers` -> it returns [0,1]
I hit F5 on the browser -> GetComputers gets hit again -> it returns [0,1,2]
P.S
I have not posted the Computer class since it does not matter in this scenario ( It implements IEquateable in case you are wondering if it is failing when i use the == operator.
The last 2 methods deal with the initialization of the Disk file.

Implementing an asynchronous call to a long running process MVC4

I have a large data set that holds up my UI so I thought I would create a background call to fill my local repository and display my other controls in the UI right away and load the results of the async call when I get a response.
I found a helpful tutorial but I am still having to wait until all my results are loaded before I see any controls.
http://www.asp.net/mvc/tutorials/mvc-4/using-asynchronous-methods-in-aspnet-mvc-4
CODE UPDATED
I have created a folder called Services and created FacilitiesService.cs in that folder, see below:
public class FacilitiesService
{
internal async Task<List<Facility>> GetFacilitiesBySourceDbAsync(string sourceDb)
{
var fac = new Facility();
var con = Connect(); // Omitted
try
{
con.Open();
}
catch (Exception ex)
{
Console.WriteLine("Error: GetFacilityBySourceDb " + ex.Message);
}
try
{
OracleDataReader reader = null;
// Requestor
var cmd = new OracleCommand("SELECT FACILITY, FACILITY_ID FROM MyTable where (source_db = '" + sourceDb + "')", con);
reader = cmd.ExecuteReader();
while (reader.Read())
{
fac.Add(new Facility()
{
FacilityName = reader["FACILITY"].ToString(),
FacilityId = reader["FACILITY_ID"].ToString()
});
}
}
catch (Exception ex)
{
throw ex;
}
finally
{
con.Close();
con.Dispose();
}
return fac;
}
}
Then in my HomeController.cs I have the following:
public class HomeController
{
public async Task<List<Facility>> FacilitiesAsync()
{
ViewBag.SyncOrAsync = "Asynchronous";
var service = new FacilitiesService();
this._facilities = new List<Facility>();
var facilities = await service.GetFacilitiesBySourceDbAsync("TEST");
foreach (var item in facilities)
{
Facility fac = new Facility()
{
FacilityName = item.FacilityName,
FacilityId = item.FacilityId
};
_facilities.Add(fac);
}
return _facilities;
}
}
This is my Facility (model) class:
public class Facility : List<Facility>
{
[Required]
[Display(Name = "Facility")]
public string FacilityName { get; set; }
public string FacilityId { get; set; }
public Facility()
{
// Default Constructor
}
public Facility(string facilityName, string facilityId)
{
this.FacilityName = facilityName;
this.FacilityId = facilityId;
}
}
I am using an Ajax call to kick off the FacilitiesAsync method in the code behind from a function call in the About.cshtml page when the user tabs off the tetbox/input control with an id of "tags", I could switch this to something else later but I get the data back when I step through the code-behind and I see both the beforeSend and complete functions fire an alert:
<script type="text/javascript">
$(function () {
var availableTags = [
// Neeed data from function call to populate this list
];
$("#tags").autocomplete({
source: availableTags
});
$("#tags").focusout(function () {
var result = null;
$.ajax({
beforeSend: function() {
alert("Testing");
},
url: "FacilitiesAsync",
success: function(data) {
result = data;
},
complete: function () {
alert(result);
}
});
});
});
</script>
#using (Html.BeginForm()) {
<div class="ui-widget">
<label for="tags">Tags: </label>
<input id="tags" />
</div>
}
This works GREAT! However, I want to take the data from the call made to the code-behind to populate the array availableTags and I'm not sure how to do that. Suggestions?
There's a few things wrong with the implementations, and one problem with the approach.
First, the GetFacilitiesBySourceDbAsync does not contain an await. The compiler will issue a warning in this situation, informing you that it's not really an asynchronous method when you do that; it will run synchronously. That's an important warning. If you want asynchronous code, you'll need to make it asynchronous all the way. This means using the asynchronous database methods (ExecuteReaderAsync, etc).
Secondly, the Task.WhenAll call in Index is meaningless (since you only pass it a single task). Also, since Index is synchronous but calls an asynchronous method, the code not shown is probably calling Result, which is a no-no on ASP.NET. As I explain on my blog, this will actually deadlock once your async code is actually asynchronous.
But even if you fix these, it won't do what you want. There's a problem with the approach, and that is that async doesn't change the HTTP protocol (this is also a link to my blog). ASP.NET MVC understands asynchronous methods and will not complete the request until the async action method completes. You'll need to use something like AJAX to get the web page to do what you want.

doc has error? not sure

http://docs.phalconphp.com/en/0.6.0/reference/odm.html
Setting multiple databases¶
In Phalcon, all models can belong to the same database connection or have an individual one. Actually, when Phalcon\Mvc\Collection needs to connect to the database it requests the “mongo” service in the application’s services container. You can overwrite this service setting it in the initialize method:
<?php
//This service returns a mongo database at 192.168.1.100
$di->set('mongo1', function() {
$mongo = new Mongo("mongodb://scott:nekhen#192.168.1.100");
return $mongo->selectDb("management");
});
//This service returns a mongo database at localhost
$di->set('mongo2', function() {
$mongo = new Mongo("mongodb://localhost");
return $mongo->selectDb("invoicing");
});
Then, in the Initialize method, we define the connection service for the model:
<?php
class Robots extends \Phalcon\Mvc\Collection
{
public function initialize()
{
$this->setConnectionService('management'); // here?
}
}
You are correct. The documentation was wrong. The correct usage is to set the appropriate service name (not the collection) using the setConnectionService.
http://docs.phalconphp.com/en/latest/reference/odm.html
<?php
// This service returns a mongo database at 192.168.1.100
$di->set(
'mongo1',
function()
{
$mongo = new Mongo("mongodb://scott:nekhen#192.168.1.100");
return $mongo->selectDb("management");
}
);
// This service returns a mongo database at localhost
$di->set(
'mongo2',
function()
{
$mongo = new Mongo("mongodb://localhost");
return $mongo->selectDb("invoicing");
}
);
Then, in the Initialize method, we define the connection service for the model:
.. code-block:: php
<?php
class Robots extends \Phalcon\Mvc\Collection
{
public function initialize()
{
$this->setConnectionService('mongo1');
}
}