I am trying to implement a chat application. Users should be able to send messages to specific users. In order to do that I need to map usernames to their connectionIDs.
My client is using custom authentication. Username is stored in Session["User"]. Therefore I don't have the username stored in Context.User.Identity.Name, which is where SignalR normally takes the username from.
How else can I get the username of the logged in user so that I can map it to Context.ConnectionID?
Here is some sample implementation of public class ChatHub : Hub I found on the web.
private readonly static ConnectionMapping<string> _connections =
new ConnectionMapping<string>();
public void SendChatMessage(string who, string message)
{
string name = Context.User.Identity.Name;
foreach (var connectionId in _connections.GetConnections(who))
{
Clients.Client(connectionId).addChatMessage(name + ": " + message);
}
}
public override Task OnConnected()
{
string name = Context.User.Identity.Name;
_connections.Add(name, Context.ConnectionId);
return base.OnConnected();
}
Pass your username using query string.
Client
First set query string
For auto generated proxy
$.connection.hub.qs = { 'username' : 'anik' };
For manual proxy
var connection = $.hubConnection();
connection.qs = { 'username' : 'anik' };
then start hub connection
Server
public override Task OnConnected()
{
var username= Context.QueryString['username'];
return base.OnConnected();
}
Related
I'm working on a simple chat using SignalR. At the moment I'm trying associate a users connection id with their identity user. I want to do this to prevent a user from impersonating another user by manually calling the hub functions.
My hub looks something like this:
public static class MessageContext
{
public static string RECEIVE = "ReceiveMessage";
public static string REGISTER = "Register";
public static string SEND = "SendMessage";
}
public class ChatHub : Hub
{
public const string HUBURL = "/api/ChatSignal";
Dictionary<string, string> _userContext;
public ChatHub()
{
_userContext = new Dictionary<string, string>();
}
public override Task OnConnectedAsync()
{
var ConnectionId = Context.ConnectionId;
var Username = Context.User.Identity.Name;
_userContext.Add(ConnectionId, Username);
Groups.AddToGroupAsync(ConnectionId, Username);
return base.OnConnectedAsync();
}
public async Task SendAll(string user, string message)
{
var ConnectionId = Context.ConnectionId;
message = HttpUtility.HtmlEncode(message);
await Clients.All.SendAsync(MessageContext.RECEIVE, _userContext[ConnectionId], message);
}
public Task SendMessage(string sender, string receiver, string message)
{
var ConnectionId = Context.ConnectionId;
message = HttpUtility.HtmlEncode(message);
return Clients.Group(receiver).SendAsync(MessageContext.RECEIVE, _userContext[ConnectionId], message);
}
public override async Task OnDisconnectedAsync(Exception e)
{
var ConnectionId = Context.ConnectionId;
var Username = Context.User.Identity.Name;
_userContext.Remove(ConnectionId);
await base.OnDisconnectedAsync(e);
}
}
My problem is that after when I call these functions, the dictionary gets set to null. After looking around for a while I found on MSDN that, hubs are "Transient", so each hub method call is executed on a new hub instance. This is a problem if I want to save ConnectionId:Identity.Name.
How can I use a dictionary to store this data for each hub instance?
To fix the null issue remove the initialization of _userContext in the constructor.
And change the line Dictionary<string, string> _userContext; -> private static ConcurrentDictionary<string, string> _userContext = new ConcurrentDictionary<string, string>();
This is how your dictionary state will be retained across different hub instances. And ConcurrentDictionary will make it thread-safe.
But it is not a very scaleable solution. If you are really making a production-grade chat application, try to use something like Redis Cache for such state management.
I have a signalR Server(Console Application) and a client application(Asp.net MVC5)
How I can send message to specific user in OAuth Membership.
Actually I can't resolve sender user from hub request context with.
Context.User.Identity.Name
My Hub
public class UserHub : Hub
{
#region Hub Methods
public void LoggedIn(string userName, string uniqueId, string ip)
{
Clients.All.userLoggedIn(userName, uniqueId, ip);
}
public void LoggedOut(string userName, string uniqueId, string ip)
{
var t = ClaimsPrincipal.Current.Identity.Name;
Clients.All.userLoggedOut(userName, uniqueId, ip);
}
public void SendMessage(string sendFromId, string userId, string sendFromName, string userName, string message)
{
Clients.User(userName).sendMessage(sendFromId, userId, sendFromName, userName, message);
}
#endregion
}
Start hub class(Program.cs)
class Program
{
static void Main(string[] args)
{
string url = string.Format("http://localhost:{0}", ConfigurationManager.AppSettings["SignalRServerPort"]);
using (WebApp.Start(url))
{
Console.WriteLine("Server running on {0}", url);
Console.ReadLine();
}
}
}
Keep connectionId with userName by creating a class as we know that Signalr only have the information of connectionId of each connected peers.
Create a class UserConnection
Class UserConnection{
public string UserName {set;get;}
public string ConnectionID {set;get;}
}
Declare a list
List<UserConnection> uList=new List<UserConnection>();
pass user name as querystring during connecting from client side
$.connection.hub.qs = { 'username' : 'anik' };
Push user with connection to this list on connected mthod
public override Task OnConnected()
{
var us=new UserConnection();
us.UserName = Context.QueryString['username'];
us.ConnectionID =Context.ConnectionId;
uList.Add(us);
return base.OnConnected();
}
From sending message search user name from list then retrive the user connectionid then send
var user = uList.Where(o=>o.UserName ==userName);
if(user.Any()){
Clients.Client(user.First().ConnectionID ).sendMessage(sendFromId, userId, sendFromName, userName, message);
}
DEMO
All of these answers are unnecessarily complex. I simply override "OnConnected()", grab the unique Context.ConnectionId, and then immediately broadcast it back to the client javascript for the client to store and send with subsequent calls to the hub server.
public class MyHub : Hub
{
public override Task OnConnected()
{
signalConnectionId(this.Context.ConnectionId);
return base.OnConnected();
}
private void signalConnectionId(string signalConnectionId)
{
Clients.Client(signalConnectionId).signalConnectionId(signalConnectionId);
}
}
In the javascript:
$(document).ready(function () {
// Reference the auto-generated proxy for the SignalR hub.
var myHub = $.connection.myHub;
// The callback function returning the connection id from the hub
myHub.client.signalConnectionId = function (data) {
signalConnectionId = data;
}
// Start the connection.
$.connection.hub.start().done(function () {
// load event definitions here for sending to the hub
});
});
In order to be able to get "Context.User.identity.Name", you supposed to integrate your authentication into OWIN pipeline.
More info can be found in this SO answer: https://stackoverflow.com/a/52811043/861018
In ChatHub Class Use This for Spacific User
public Task SendMessageToGroup(string groupName, string message)
{
return Clients.Group(groupName).SendAsync("Send", $"{Context.ConnectionId}: {message}");
}
public async Task AddToGroup(string groupName)
{
await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
await Clients.Group(groupName).SendAsync("Send", $"{Context.ConnectionId} has joined the group {groupName}.");
}
public async Task RemoveFromGroup(string groupName)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
await Clients.Group(groupName).SendAsync("Send", $"{Context.ConnectionId} has left the group {groupName}.");
}
I need to pass User.Identity.Name to Windows Form client.
Method
public override Task OnConnected() {
string userName = Context.User.Identity.Name;
string connectionId = Context.ConnectionId;
var user = Users.GetOrAdd(userName, _ => new User {
Name = userName,
ConnectionIds = new HashSet<string>()
});
lock (user.ConnectionIds) {
user.ConnectionIds.Add(connectionId);
if (user.ConnectionIds.Count == 1) {
Clients.Others.userConnected(userName);
}
}
return base.OnConnected();
}
But Context.User.Identity.Name is null? Why? How to solve it?
It looks like you are trying to get the username when connecting to the hub. I solved a similar issue by passing the username from my client. It also sounds like you are making use of the SignalR .NET client. Give this a try
Client
Connection = new HubConnection("http://.../", new Dictionary<string, string>
{
{ "UserName", WindowsIdentity.GetCurrent().Name }
});
Hub
public override Task OnConnected()
{
string userName = Context.QueryString["UserName"]
}
I try to code a website with webservice in it.
My login page code is:
protected void button_Click(object sender, EventArgs e)
{
DBKariyerBL.User user = new DBKariyerBL.User();
user.UserName = txtKlnAd.Text;
user.Password = txtParola.Text;
if (user.Login(user))
{
Session["LogIn"] = "1";
Response.Redirect("Default.aspx");
}
}
and my User.cs page in business layer is:
public class User
{
public string UserName { get; set; }
public string Password { get; set; }
SqlConnection conn = new SqlConnection(Util.ConnectionString);
public bool Login(User user)
{
return Convert.ToInt32(SqlHelper.ExecuteScalar(conn, CommandType.Text, "SELECT COUNT(*) FROM Users WHERE UserName=#UserName AND Password=#Password", new SqlParameter[] { new SqlParameter("#UserName", user.UserName), new SqlParameter("#Password", user.Password) })) > 0;
}
}
My User.asmx page is like:
public class User : System.Web.Services.WebService
{
[WebMethod]
public bool Login(DBKariyerBL.User user)
{
DBKariyerBL.User kull = new DBKariyerBL.User();
return kull.Login(user);
}
}
and I have a Util.cs for SQL connection:
public class Util
{
public static string ConnectionString
{
get { return ConfigurationManager.ConnectionStrings["SqlConnection"].ConnectionString; }
}
}
When I run this project in my local server, is working fine, but when I try to put this code in my test server, it gives an error page:
Cannot open database "DenizBankKariyerDB" requested by the login. The login failed.
Login failed for user 'IIS APPPOOL\DenizKariyer'.
I'm stuck. I think the problem is in my Util.cs file.
The error is clear: the login provided in the connection string for the database you're trying to connect to is not valid. The user does not exist, or they don't have rights applied to the catalog, or the password is incorrect, etc.
The way to solve this is to check your database, confirm the user details, and apply them in the connection string accordingly.
since you added integrated security = true the sql server uses the current Windows account credentials are used for authentication.What you want is to use is the username and password from the forms .
So instead of using the connection string from the config try to use the something like this.
public class Util
{
public static string ConnectionString
{
get { return "Data Source=ServerName;" +
"Initial Catalog=DataBaseName;" +
"User id=UserName;" +
"Password=pwd;"; }
}
}
you can have a method that takes server, username ,password as parameters.
I think perhaps that I do not fully understand the correct way to implement groups in SignalR :)
I am using a SignalR hub coupled with some JS.
The relevant code looks as follows:
public class NotificationHub : Hub
{
public void RegisterUser()
{
if (Context.User.Identity.IsAuthenticated)
{
var username = Context.User.Identity.Name;
Groups.Add(Context.ConnectionId, username);
//check roles
var roles = Roles.GetRolesForUser(username);
foreach (var role in roles)
{
Groups.Add(Context.ConnectionId, role);
}
}
}
public override Task OnConnected()
{
RegisterUser();
return base.OnConnected();
}
//rejoin groups if client disconnects and then reconnects
public override Task OnReconnected()
{
RegisterUser();
return base.OnReconnected();
}
}
Stepping through this code suggests that it works as intended.
When I actually come to send a message however, broadcasting to ALL works. If I try and broadcast to a particular user through their username (their own specific group) nothing happens.
public void BroadcastNotification(List<string> usernames, Notification n)
{
var context = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
foreach (var username in usernames)
{
context.Clients.Group(username).broadcastMessage(new NotificationPayload()
{
Title = n.Title,
Count = UnitOfWork.NotificationRepository.GetCount(),
Notification = n.Body,
Html = RenderPartialViewToString("_singleNotification", n)
});
}
}
It would appear that groups do not work as I had thought. Is there a step that I am missing here?
I don't see your client code, but I think you have to explicitly start the hub, and "join" the "group" before you receive the "notifications". So in your client code, something like
$.connection.hub.start()
.done(function() {
chat.server.join();
});
and in your hub, a "Join" method something like what you already have:
public Task Join()
{
if (Context.User.Identity.IsAuthenticated)
{
var username = Context.User.Identity.Name;
return Groups.Add(Context.ConnectionId, username);
}
else
{
// a do nothing task????
return Task.Factory.StartNew(() =>
{
// blah blah
});
}
}