SignalR: Chat between 2 identical database codes - asp.net-mvc

I have an operator table as follows:
id, code, name
And I have a table for the logged in user:
id, code, userid
I want the same chat codes to see each other.
in my chathub:
public void Send(string name, string message)
{
Clients.All.broadcastMessage(name, message);
}
and in my view:
$(function () {
var chat = $.connection.chatHub;
chat.client.broadcastMessage = function (name, message) {
var encodedName = $('<div />').text(name).html();
var encodedMsg = $('<div />').text(message).html();
$('#discussion').append('<li style="background-color: #dfdfed;margin-bottom:2px;border-radius:4px;padding:10px;"><strong>' + encodedName
+ '</strong>: ' + encodedMsg + '</li>');
var scr = $('#uDIV')[0].scrollHeight;
$('#uDIV').animate({ scrollTop: scr });
};
$('#message').focus();
$.connection.hub.start().done(function () {
$('#sendmessage').click(function () {
chat.server.send($('#displayname').val(), $('#message').val());
$('#message').val('').focus();
});
});
});

Related

jquery autocomplete passing parameters

I need help to pass parameters through JQuery's autocomplete. I have an input:
<input type="text" class="form-control mb-2 mr-sm-2 mb-sm-0" name="searchName" id="searchName" placeholder="Nom et/ou prénom" />
inside a form. When you type, an jquery autocomplete function launches a search in Active Directory and shows result in the drop down list:
$(document).ready(function () {
$("#searchName").autocomplete({
source: function (request, response) {
$.ajax({
url: "/Home/SearchUserWhileTyping",
type: "GET",
data: { name: $("#searchName").val() },
contentType: "application/json;charset=utf-8",
dataType: "json",
success: function (data) {
$("#searchName").html(''),
response($.map(data, function (item) {
return {
label: item
}
}));
}
});
},
minLength: 4
})
});
$(document).ready(function(){
$('#searchName').on('autocompletechange change', function () {
$('#searchValue').html('You selected: ' + this.value);
}).change()});
For now I could only do it after a form validation: form is validated -> I load the users found and their unique id -> click on one link and it shows the user info thanks to their unique id passed through. What I want to do is: If you click on one of the autocomplete choices, it directly shows the information of your user.
Here is the code for the search while you type:
[HttpGet]
public ActionResult SearchUserWhileTyping(string name)
{
ADManager adManager = new ADManager();
List<string> lastName = adManager.SearchUserByName(name);
List<string> splitList = new List<string>();
if (lastName != null)
{
if (lastName.Count <= 10)
{
int inc = 0;
foreach(string splitter in lastName)
{
if (inc % 2 == 1)
{
splitList.Add(splitter);
}
inc++;
}
return Json(splitList, JsonRequestBehavior.AllowGet);
}
}
return null;
}
I use a splitter because another function allows me to search AD and returns several parameters (which will then be useful to immediately find a user by its unique id, that's my difficulty).
This calls the following function:
public List<string> SearchUserByName(string name)
{
try
{
DirectoryEntry ldapConnection = createDirectoryEntry();
DirectorySearcher search = new DirectorySearcher(ldapConnection);
var sidInBytes=new byte[0];
//anr permet de chercher tout utilisateur contenant "name"
search.Filter = "(&(objectClass=user)(anr=" + name + "))";
//search.Filter = "(&(objectClass=User) (name=" + name + "*))";
search.PropertiesToLoad.Add("displayName");
search.PropertiesToLoad.Add("distinguishedName");
resultCollection = search.FindAll();
if (resultCollection.Count == 0)
{
return null;
}
else
{
foreach(SearchResult sResult in resultCollection)
{
if (sResult.Properties["distinguishedName"][0].Equals(null) ||
sResult.Properties["displayName"][0].Equals(null))
continue;
displayName.Add(sResult.Properties["distinguishedName"][0].ToString());
displayName.Add(sResult.Properties["displayName"][0].ToString());
}
}
ldapConnection.Close();
ldapConnection.Dispose();
search.Dispose();
return displayName;
}
catch (Exception e)
{
Console.WriteLine("Exception caught:\n\n" + e.ToString());
}
return null;
}
Finally, when I have my list of users, I can click on their link and I load info about the user using this function:
public List<KeyValuePair<string,string>> GetUserInfoBySAMAN(string sAMAccountName)
{
try
{
DirectoryEntry ldapConnection = createDirectoryEntry();
DirectorySearcher search = new DirectorySearcher(ldapConnection);
search.Filter = "(sAMAccountName=" + sAMAccountName + ")";
search.PropertiesToLoad.Add("objectSID");
search.PropertiesToLoad.Add("displayName");
search.PropertiesToLoad.Add("memberOf");
search.PropertiesToLoad.Add("description");
search.PropertiesToLoad.Add("accountExpires");
search.PropertiesToLoad.Add("sAMAccountName");
result = search.FindOne();
///Conversion du SID en chaine de caractères
var sidInBytes = (byte[])result.Properties["objectSID"][0];
var sid = new SecurityIdentifier(sidInBytes, 0);
String time;
if (result.Properties["accountExpires"][0].ToString().Equals("0")|| result.Properties["accountExpires"][0].ToString().Equals("9223372036854775807"))
{
time = "Jamais";
}
else
{
///Conversion de la date en entier puis en date
DateTime dt = new DateTime(1601, 01, 02).AddTicks((Int64)result.Properties["accountExpires"][0]);
time = dt.ToString();
}
string desc="";
if (result.Properties.Contains("description"))
{
desc = result.Properties["description"][0].ToString();
}
else
{
desc = "Pas de description disponible";
}
userInfo = new List<KeyValuePair<string, string>>()
{
new KeyValuePair<string, string>("displayName",result.Properties["displayName"][0].ToString()),
new KeyValuePair<string, string>("memberOf", result.Properties["memberOf"][0].ToString()),
new KeyValuePair<string, string>("accountExpires",time),
new KeyValuePair<string, string>("description",desc),
new KeyValuePair<string, string>("sid",sid.ToString()),
new KeyValuePair<string, string>("sAMAccountName",result.Properties["sAMAccountName"][0].ToString())
/*lastName.Add(result.Properties["displayName"][0].ToString());
lastName.Add(result.Properties["memberOf"][0].ToString());
lastName.Add(sid.ToString());
lastName.Add(result.Properties["accountExpires"][0].ToString());
lastName.Add(result.Properties["description"][0].ToString());*/
};
return userInfo;
}
catch(Exception e)
{
Console.WriteLine("Exception caught:\n\n" + e.ToString());
}
return null;
}
That last function doesn't work if I change sAMAccountName by distinguishedName because apparently this attribute cannot be used like that. I want to use distinguishedName and immediately have my object.
So what I need is to search while I type, and if I select one of the proposed choices, validating the form immediately send me to user info page.
Thanks for your help, hope it is clear enough
Edit I added a 2nd script that can get the value of selected item, but I need the data passed through the controller
If I understood right, autocomplete method has select event.
$(document).ready(function () {
$("#searchName").autocomplete({
source: function (request, response) {
$.ajax({
url: "/Home/SearchUserWhileTyping",
type: "GET",
data: { name: $("#searchName").val() },
contentType: "application/json;charset=utf-8",
dataType: "json",
success: function (data) {
$("#searchName").html(''),
response($.map(data, function (item) {
return {
label: item
}
}));
},
select: function(e, ui)
{
//Just Example
$.get("UserController", {ID: ui.Id}).done(
function(data){
});
//Write your ajax post or get method
//that fetches user data directly as soon as
//the item in list clicked
}
});
},
minLength: 4
})
});
Edit: I saw your edit, so that you can use your GetUserInfoBySAMAN function for ajax get request inside select event (instead where I wrote "UserController"), and you have work for binding return data to the inputs or labels as well.

Socket.io, redis and redis message queue

I am using SocketIO and redis based on the following code,
var sub = redis.createClient();
var pub = redis.createClient();
sub.subscribe('chat');
io.use(socketHandshake({store: sessionStore, key:'jsessionid', secret:'secret', parser:cookieParser()}));
io.on('connection', function (socket) {
socket.on('chat', function (message) {
// io.emit('chat', "hello world");
pub.publish('chat', "hello world");
});
sub.on('message', function (channel, message) {
io.emit(channel, message);
});
});
This is the base code. I have modified the code so that if any user goes offline, in server side I am storing the messages in RSMQ(Redis Simple Message Queue) and when user comes online, the message is fetched from queue and emits to the user.I have used the following code to achieve this.I have stored the user status in an array.
var fs = require('fs')
, http = require('http')
, socketio = require('socket.io');
var redis = require('redis');
var store = redis.createClient();
var pub = redis.createClient();
var sub = redis.createClient();
RedisSMQ = require("rsmq");
rsmq = new RedisSMQ( {host: "127.0.0.1", port: 6379, ns: "rsmq"} );
var active_users=[];
var inactive_users=[];
var user_status=[];
var channel_users=[];
var users_queue=[];
var socket_ids=[];
var cname,qn;
var clients=[];
var server = http.createServer(function(req, res) {
res.writeHead(200, { 'Content-type': 'text/html'});
res.end(fs.readFileSync(__dirname + '/index.html'));
}).listen(9000, function() {
console.log('Listening at: http://localhost:9000');
});
socketio.listen(server).on('connection', function (socket) {
socket.on('login', function(data){
console.log('a user ' + data.userId + ' connected'+socket.id);
//saving userId to array with socket ID
active_users[socket.id] = data.userId;
socket_ids[data.userId]=socket.id;
clients[socket.id] = socket;
user_status[data.userId]="online";
});
socket.on('message', function (msg) {
console.log('Message Received: ', msg);
socket.broadcast.emit('message', msg);
});
socket.on('json', function (msg) {
if(msg.channel_name=='UserState'){
rsmq.listQueues(function(err,resp){
//console.log("QUEUES LIST"+resp);
});
if(msg.user_state=='active'){
store.hmset("active_users."+msg.sender_id,{"user":"online"});
user_status[msg.sender_id]="online";console.log(user_status);
if(users_queue[msg.sender_id]!=undefined && users_queue[msg.sender_id].length>0){
console.log("USERS QUEUE:"+users_queue[msg.sender_id]['0']);
for(var i=0;i<users_queue[msg.sender_id].length;i++){
cname=users_queue[msg.sender_id][i].split('_')[0];//get channel name from queue name
qn=users_queue[msg.sender_id][i];
rsmq.getQueueAttributes({qname:users_queue[msg.sender_id][i]},function(err,resp){
console.log("RESP:"+resp.msgs);
if(resp.msgs>0){ //if there are messages in queue......
for(var j=0;j<resp.msgs;j++){
rsmq.popMessage({qname:qn},function(err,resp){
console.log(resp);
var sid=socket_ids[msg.sender_id]; console.log("SOCKETID:"+sid); //get socket.id for the user
pub.publish(cname,resp.message);
});
}
}
});
}
}
}
else{
store.hmset("active_users."+msg.sender_id,{"status":"offline"});
user_status[msg.sender_id]="offline";
}
}
if(msg.channel_name=='ShareConversation'){
var channel=msg.conversations_data.conversation_id;//have to change to conversation_id or whatever channel.....
sub.subscribe(channel);
channel_users[channel]=[];
var m=msg.conversations_data.users.split(',');
for(var i=0;i<m.length;i++){
channel_users[channel].push(m[i]);
}
for(var i=0;i<channel_users[channel].length;i++){
var q=channel_users[channel][i].split('#')[0].replace(/(^\s+|\s+$)/g, '');
var queue_name=channel+"_"+q;console.log(queue_name);
var uname=channel_users[channel][i].replace(/(^\s+|\s+$)/g, '');
users_queue[uname]=[];
users_queue[uname].push(queue_name);
rsmq.createQueue({qname:queue_name}, function (err, resp) {
console.log(err);
console.log(queue_name);
if (resp===1) {
console.log("queue created");
}
});
}
}
socket.broadcast.emit('json', msg);
});
sub.on('message', function (channel, message) {
console.log("Message: " + message);
for(var i=0;i<channel_users[channel].length;i++){
var c=channel_users[channel][i].replace(/(^\s+|\s+$)/g, '');console.log("channel_users:"+channel_users[channel][i]);console.log("USER STATE :"+ user_status[c]);
if(user_status[c]=='offline'){
//send notification.........
//put messages in queue.......
var q=channel_users[channel][i].split('#')[0].replace(/(^\s+|\s+$)/g, '');
var queue_name=channel+"_"+q;console.log(queue_name);
rsmq.sendMessage({qname:queue_name, message:message}, function (err, resp) {
console.log(err);
if (resp) {
console.log("Message sent. ID:", resp);
}
});
}
}
socket.emit(channel, message);
});
});
This is my entire code. Here the issue is when the user goes offline, the message gets saved in queue multiple times and when the user comes online, the messages are received multiple times as there are duplicate messages saved in queue. How to overcome this.Please help....

.Net RunTimeBinderException

I have a data content witch contains complex data I just need index names which seem in Dynamic View in data. In debug mode I can see the datas but cant get them..
You can see the contents of data in image below):
if(hits.Count() > 0)
{
var data = hits.FirstOrDefault().Source;
var dataaa = JsonConvert.DeserializeObject(hits.FirstOrDefault().Source);
}
I found a solution with checking if selected index has documents; if yes,
I take the first document in index, and parse it to keys(field names) in client.
here is my func:
[HttpPost]
public ActionResult getIndexFields(string index_name)
{
var node = new Uri("http://localhost:9200");
var settings = new ConnectionSettings(
node,
defaultIndex: index_name
);
var esclient = new ElasticClient(settings);
var Documents = esclient.Search<dynamic>(s => s.Index(index_name).AllTypes().Source());
string fields = "";
if (Documents.Documents.Count() > 0)
{
fields = JsonConvert.SerializeObject(Documents.Documents.FirstOrDefault());
var data = Documents.Documents.FirstOrDefault().Source;
return Json(new
{
result = fields,
Success = true
});
}
return Json(new
{
result = "",
Success = false
});
}
and my js:
$.ajax({
url: "getIndexFields?index_name=" + $(this).val(),
type: "POST",
success: function (data) {
if (data.Success) {
var fields = JSON.parse(data.result);
var fields_arr = [];
$.each(fields, function (value, index) {
fields_arr.push(
{
id: value,
label: value
})
});
options.filters = fields_arr;
var lang = "en";//$(this).val();
var done = function () {
var rules = $b.queryBuilder('getRules');
if (!$.isEmptyObject(rules)) {
options.rules = rules;
}
options.lang_code = lang;
$b.queryBuilder('destroy');
$('#builder').queryBuilder(options);
};
debugger
if ($.fn.queryBuilder.regional[lang] === undefined) {
$.getScript('../dist/i18n/query-builder.' + lang + '.js', done);
}
else {
done();
}
}
else {
event.theme = 'ruby';
event.heading = '<i class=\'fa fa-warning\'></i> Process Failure';
event.message = 'User could not create.';
event.sticky = true;
$("#divfailed").empty();
$("#divfailed").hide();
$("#divfailed").append("<i class='fa fa-warning'></i> " + data.ErrorMessage);
$("#divfailed").show(500);
setTimeout(function () {
$("#divfailed").hide(500);
}, 3000);
}
},
error: function (a, b, c) {
debugger
event.theme = 'ruby';
event.heading = '<i class=\'fa fa-warning\'></i> Process Failure';
event.message = 'Object could not create.';
$("#divfailed").empty();
$("#divfailed").hide();
msg = c !== "" ? c : a.responseText;
$("#divfailed").append("<i class='fa fa-warning'></i> " + msg);
$("#divfailed").show(500);
setTimeout(function () {
$("#divfailed").hide(500);
}, 3000);
}
});

Can't invoke client functions from the Server

I'm working on a small concept trying to get to work with SignalR.
In this scenario all connected clients need to refresh when one of the clients perform an update, this update is performed through an Ajax request.
Because there might be different instances of the page, depending on a parameter, I'm using groups for this.
The client side code looks as followed:
<script>
window.onload = function () {
DoRefresh();
}
$(function () {
var hub = $.connection.commonHub;
hub.client.refresh = function () {
DoRefresh();//This doesn't get called for the other clients
};
$.connection.hub.start();
$.connection.hub.start().done(function () {
hub.server.join("MyPage" + #Model.Id + "");
});
});
function htmlEncode(value) {
var encodedValue = $('<div />').text(value).html();
return encodedValue;
}
function DoRefresh() {
$.ajax({
url: '#Url.Action("LoadData")',
cache: false,
data: "&id=" + #Model.Id + "",
success: function (html) {
$("#conversation").empty();
$("#conversation").append(html);
},
error: function (xhr, status, err) {
alert('Response code:' + xhr.status + '\r\n[Error:' + err + '] ' + status);
}
});
return false;
};
function DoUpdate() {
$.ajax({
url: '#Url.Action("DoUpdate")',
cache: false,
data: "&id=" + #Model.Id + "&posterID=" + #userAccount.AccountID + "&message=" + $("#Textbox").val().replaceAll("\n", "[br]"),
success: function () {
$("#Textbox").empty();
DoRefresh();
},
error: function () {
$("#Textbox").empty();
DoRefresh();
}
});
return false;
};
</script>
In my controller, following functions are part of this scenario:
public class MyController : Controller
{
private Hubs.CommonHub hub = new Hubs.CommonHub();
//Some other Controller Methods
public PartialViewResult LoadData(int id)
{
MyModel item = Connection.DB.MyData.FirstOrDefault(x => x.Id == id);
return PartialView(item);
}
public virtual EmptyResult DoUpdate(int id, int posterID, string message)
{
message = message.Replace("[br]", "\r\n");
MyModel item = Connection.DB.MyData.FirstOrDefault(x => x.Id == id);
Account poster = Connection.DB.Accounts.FirstOrDefault(x => x.Id == posterID);;
item.Updates.Add(new Update()
{
PosterId = posterID,
Poster = poster,
Id = id,
Item = item,
PostDate = DateTime.UtcNow,
Message = message.Replace(Environment.NewLine,"\r\n")
});
Connection.DB.SaveChanges();
hub.Refresh("MyPage" + item.Id);
return null;
}
}
And finally, my hub class looks like this:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using Microsoft.AspNet.SignalR;
using MyProject.Models;
namespace MyProject.Hubs
{
public class CommonHub : Hub
{
private string myInfo;
public override Task OnConnected()
{
myInfo = Context.ConnectionId;
return base.OnConnected();
}
public Task Join(string groupName)
{
return Groups.Add(Context.ConnectionId, groupName);
}
public Task Leave(string groupName)
{
return Groups.Remove(Context.ConnectionId, groupName);
}
public void Refresh(string groupName)
{
var context = GlobalHost.ConnectionManager.GetHubContext<CommonHub>();
context.Clients.Group(groupName).Refresh();
}
}
}
The Join task in the hub is called with every connect of a new browser window. The Refresh method is invoked on the hub, but only the invoking window has its page refreshed. The client side refresh function only gets invoked once when debugging, probably for the invoking client.
I have no idea why the other clients are not doing their updates, please help.

How to improve the solution

This solution works, but I think it can much better be done
JQuery:
$('#addMessage').click(function () {
var textMessage = $('#ticketMessage').val();
var isInternal = $('#isInternal')[0].checked;
var ticketID = $('#TicketID').val();
$.ajax({
url: '/Ticket/AddMessage',
type: 'POST',
data: { textMessage: textMessage, isInternal: isInternal, ticketID: ticketID },
success: function (data) {
var tbody = $('#allMessages').children()[0];
tbody.innerHTML = tbody.innerHTML + data;
$('#ticketMessage').val("");
$('#isInternal')[0].checked = false;
}
});
});
controller
public string AddMessage(string textMessage, bool isInternal, int ticketID)
{
Message message = new Message();
message.IsInternal = isInternal;
message.TicketMessage = textMessage;
message.TicketID = ticketID;
DateTime created=DateTime.Now;
message.CreatedDateTime = created;
message.PersonID = AppSecurity.Security.GetPersonID(Session);
var personRepository = new PersonRepository(_context);
MessageRepository messageRepository = new MessageRepository(_context);
messageRepository.Add(message);
_context.SaveChanges();
string relSrc = (personRepository.GetById((int)message.PersonID) as Employee).Image;
string source = "";
string isInternalStr = "";
if (message.IsInternal)
isInternalStr = "Internal";
if (message.Person is Employee) { source = relSrc != null ? "../../Images/TicketFiles" + relSrc.Replace('\\', '/') : "../../Images/TicketFiles/Employees/no-profile.png"; }
String response = "<tr><td style=\"width: 25%\" valign=\"top\"><table><tr>"
+ "<td><img src=\""+source+"\" alt=\"\" style=\"height: 60px\"/></td>"
+ "</tr><tr><td>"
+ AppSecurity.Security.GetUserFullName(Session)
+ "</td></tr><tr><td>"
+ created.ToString("dd.MM.yyyy") + " - " + created.ToString("HH:mm:ss")
+ "</td></tr></table></td><td style=\"width: 75%; padding:0px;\" valign=\"top\"><table style=\"width: 100%; height: 130px\" cellspacing=\"0\" cellpadding=\"0\">"
+ "<tr><td style=\"height: 20px; padding: 0px\">" + isInternalStr + "</td></tr><tr><td valign=\"top\">" + message.TicketMessage + "</td><tr></table></td></tr>";
return response;
}
Instead of generating the markup in the controller why not do it client side in the JavaScript? Your controller should not be concerned with markup
It seems to me you can benefit a lot by using jquery templates
Your controller should be of type ActionResult and as mr.nicksta said, should be made in a view.
public ActionResult AddMessage(string textMessage, bool isInternal, int ticketID)
{
...
return View(message);
}
and then create a view with the same as the controller, strongly-typed with Message.
<%# Page Language="C#" Inherits="System.Web.Mvc.ViewPage<Message>" %>
Where you create the display. You could even do this as a partial view to it can be included in other pages more easily.

Resources