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
});
}
}
Related
I want to make my discord bot respond exclusively to people with a #Member role, so when a person without that role writes a command (ex. >say Hello), the bot will not respond to it, but when a person with the #Member role writes that, it will.
Ideally, if this is being used for a command or rather multiple commands, a precondition should be used (example administration commands). This allows you to not have to duplicate the checks within each command.
Ah example of such a precondition can be found here.
You can read more on preconditions here
You will need to add role validation to each of your commands.
The quick and easy way would be to do the following:
[Command("test")]
public async Task TestCommand()
{
var user as Context.User as IGuildUser;
var roleToValidate = Context.Guild.Roles.First(r => r.Name == "SomeRoleName");
if (!user.RoleIDs.Any(r => r == roleToValidate.Id))
return;
// the rest of the code
}
Another approach (which I would recommend) is using PreconditionAttribute
/// CheckRole.cs
using Discord;
using Discord.Commands;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Example.Attributes
{
public class CheckRole : PreconditionAttribute
{
private List<string> _roles;
public CheckRole(params string[] roles)
{
_roles = roles.ToList();
}
public async override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command)
{
var user = context.User as IGuildUser;
var discordRoles = context.Guild.Roles.Where(gr => _roles.Any(r => gr.Name == r));
foreach (var role in discordRoles)
{
var userInRole = user.RoleIds.Any(ri => ri == role.Id);
if (userInRole)
{
return await Task.FromResult(PreconditionResult.FromSuccess());
}
}
return await Task.FromResult(PreconditionResult.FromError("You do not have permission to use this role."));
}
}
}
/// WhateverClassThatYouWriteCommandsIn.cs
[Command("test")]
[CheckRole("AdminRoleName", "ModeratorRole", "NormalRole")]
public async Task TestCommandForMostRoles()
{
var user as Context.User as IGuildUser;
var roleToValidate = Context.Guild.Roles.First(r => r.Name == "Some Role Name");
if (!user.RoleIDs.Any(r => r == roleToValidate.Id))
return;
// the rest of the code
}
[Command("test")]
[CheckRole("AdminRoleName", "ModeratorRole")]
public async Task TestCommandForAdmins()
{
var user as Context.User as IGuildUser;
var roleToValidate = Context.Guild.Roles.First(r => r.Name == "Some Role Name");
if (!user.RoleIDs.Any(r => r == roleToValidate.Id))
return;
// the rest of the code
}
This literal code may not work as I haven't tested it, however it is based on my own implementation of role preconditions authorisation which works.
To break down the code:
I have a variable to store multiple role names and use params[] in the constructor to allow any amount of role names to be provided. They are stored in the variable.
private List<string> _roles;
public CheckRole(params string[] roles)
{
_roles = roles.ToList();
}
CheckPermissionsAsync is automatically called every time that particular command is called.
public async override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command)
Get the actual Role objects from the context from the names, loop through them and check the user to see if they have that permission. The first time a role is found on a user it will return a success and the command code in the original command function will be run. If FromError is returned then the command code is not run.
var user = context.User as IGuildUser;
var discordRoles = context.Guild.Roles.Where(gr => _roles.Any(r => gr.Name == r));
foreach (var role in discordRoles)
{
var userInRole = user.RoleIds.Any(ri => ri == role.Id);
if (userInRole)
{
return await Task.FromResult(PreconditionResult.FromSuccess());
}
}
return await Task.FromResult(PreconditionResult.FromError("You do not have permission to use this role."));
This might seem like a lot, but you do not need to re-write role authorisation code again and you can simply add this attribute to whatever commands you want. You can also add this attribute to the class if you want every command in that class to be authorised by the role:
[CheckRoles("Moderator", "LowLevelModerator")]
public class ModeratorCommands : ModuleBase<SocketCommandContext>
{
[Command("CheckStats")]
public async Task ModeratorCommandForCheckStats()
{
// the code
}
[Command("BanUser")]
public async Task ModeratorCommandForBanUser()
{
// the code
}
[CheckRole("Admin")]
[Command("BanUser")]
public async Task ModeratorCommandOnlyForAdminsForBanModerator()
{
// the code
}
}
I've implemented ng-chat https://github.com/rpaschoal/ng-chat (SignalR).
I have 3 users: User1, User2 and User3
If I send a message from User1 to User2 it works well User2 receives the message, but if I create a group (with User1 I open User2's chat and then Add the User3) a new group is created with Users (User2 and User3).
So, when I send a message from this new chat, the users (User2 and User3) doesn't receive any message
Here is my SingalR Hub:
using AdvansysOficina.Api._Core.Infraestructura;
using AdvansysOficina.Api.Generales.Servicios.UsuarioNs;
using Microsoft.AspNetCore.SignalR;
using NgChatSignalR.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace AdvansysOficina.Api.Desarrollo.Servicios.ConversacionPuntoNs.HubNs
{
public class ConversacionHub : Hub
{
private static List<ParticipantResponseViewModel> AllConnectedParticipants { get; set; } = new List<ParticipantResponseViewModel>();
private static List<ParticipantResponseViewModel> DisconnectedParticipants { get; set; } = new List<ParticipantResponseViewModel>();
private readonly object ParticipantsConnectionLock = new object();
private ISesion _sesion;
private IUsuarioServicio _usuarioServicio;
public ConversacionHub(ISesion sesion, IUsuarioServicio usuarioServicio)
{
_sesion = sesion;
_usuarioServicio = usuarioServicio;
}
public static IEnumerable<ParticipantResponseViewModel> ConnectedParticipants(string currentUserId)
{
return AllConnectedParticipants
.Where(x => x.Participant.Id != currentUserId);
}
public void Join(string userName, dynamic grupo)
{
lock (ParticipantsConnectionLock)
{
AllConnectedParticipants.Add(new ParticipantResponseViewModel()
{
Metadata = new ParticipantMetadataViewModel()
{
TotalUnreadMessages = 0
},
Participant = new ChatParticipantViewModel()
{
DisplayName = userName,
Id = Context.ConnectionId,
}
});
// This will be used as the user's unique ID to be used on ng-chat as the connected user.
// You should most likely use another ID on your application
//Clients.Caller.SendAsync("generatedUserId", Context.ConnectionId);
Clients.Caller.SendAsync("generatedUserId", Context.ConnectionId);
Clients.All.SendAsync("friendsListChanged", AllConnectedParticipants);
}
}
public void SendMessage(MessageViewModel message)
{
var sender = AllConnectedParticipants.Find(x => x.Participant.Id == message.FromId);
if (sender != null)
{
Clients.Client(message.ToId).SendAsync("messageReceived", sender.Participant, message);
}
}
public override Task OnDisconnectedAsync(Exception exception)
{
lock (ParticipantsConnectionLock)
{
var connectionIndex = AllConnectedParticipants.FindIndex(x => x.Participant.Id == Context.ConnectionId);
if (connectionIndex >= 0)
{
var participant = AllConnectedParticipants.ElementAt(connectionIndex);
AllConnectedParticipants.Remove(participant);
DisconnectedParticipants.Add(participant);
Clients.All.SendAsync("friendsListChanged", AllConnectedParticipants);
}
return base.OnDisconnectedAsync(exception);
}
}
public override Task OnConnectedAsync()
{
lock (ParticipantsConnectionLock)
{
var connectionIndex = DisconnectedParticipants.FindIndex(x => x.Participant.Id == Context.ConnectionId);
if (connectionIndex >= 0)
{
var participant = DisconnectedParticipants.ElementAt(connectionIndex);
DisconnectedParticipants.Remove(participant);
AllConnectedParticipants.Add(participant);
Clients.All.SendAsync("friendsListChanged", AllConnectedParticipants);
}
return base.OnConnectedAsync();
}
}
}
}
My signalR Adapter (Angular)
import { ChatAdapter, Message, ParticipantResponse, Group, IChatController } from 'ng-chat';
import { map, catchError } from 'rxjs/operators';
import { HttpClient } from '#angular/common/http';
import * as signalR from '#aspnet/signalr';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { AlertasHelper } from '../../../shared/helpers/alertas.helper';
import { PushNotificationHelper } from './notifications/push-notification';
export class SignalRAdapter extends ChatAdapter {
public static serverBaseUrl = 'http://192.168.16.51:5021/'; // if running locally
public userId: string;
private grrupo;
private hubConnection: signalR.HubConnection;
constructor(private username: string, private http: HttpClient, private notification: PushNotificationHelper
) {
super();
this.initializeConnection();
}
private initializeConnection(): void {
this.hubConnection = new signalR.HubConnectionBuilder()
.withUrl(`${SignalRAdapter.serverBaseUrl}chat`, { transport: signalR.HttpTransportType.LongPolling })
.build();
this.hubConnection
.start()
.then(() => {
this.joinRoom();
this.initializeListeners();
})
.catch(err => console.log(`Error while starting SignalR connection: ${err}`));
}
private initializeListeners(): void {
this.hubConnection.on('generatedUserId', (userId) => {
// With the userId set the chat will be rendered
this.userId = userId;
});
this.hubConnection.on('messageReceived', (participant, message) => {
// Handle the received message to ng-chat
console.log(message);
this.notification.notify('Nuevo mensaje de: ' + participant.displayName, message);
this.onMessageReceived(participant, message);
});
this.hubConnection.on('friendsListChanged', (participantsResponse: Array<ParticipantResponse>) => {
// Handle the received response to ng-chat
this.onFriendsListChanged(participantsResponse.filter(x => x.participant.id !== this.userId));
});
}
joinRoom(): void {
if (this.hubConnection && this.hubConnection.state === signalR.HubConnectionState.Connected) {
this.hubConnection.send('join', this.username, '');
}
}
listFriends(): Observable<ParticipantResponse[]> {
// List connected users to show in the friends list
// Sending the userId from the request body as this is just a demo
// return this.http
// .post(`${SignalRAdapter.serverBaseUrl}listFriends`, { currentUserId: this.userId })
// .pipe(
// map((res: any) => res),
// catchError((error: any) => Observable.throw(error.error || 'Server error'))
// );
return of([]);
}
getMessageHistory(destinataryId: any): Observable<Message[]> {
// This could be an API call to your web application that would go to the database
// and retrieve a N amount of history messages between the users.
return of([]);
}
sendMessage(message: Message): void {
if (this.hubConnection && this.hubConnection.state === signalR.HubConnectionState.Connected) {
console.log(message);
this.hubConnection.send('sendMessage', message);
}
}
groupCreated(group: Group): void {
console.log( group);
}
}
Use of component
<ng-chat #chat *ngIf="signalRAdapter && signalRAdapter.userId"
[adapter]="signalRAdapter"
[userId]="signalRAdapter.userId"
[groupAdapter]="signalRAdapter"
(onParticipantChatOpened)="chatOpened($event)"
[historyEnabled]="false">
</ng-chat>
I've downloaded the example of github's creator page, but he doesn't have an example with signalr using groups, I hope you can help me.
ng-chat treats groups as individual participants. You will have to join your room when this event gets invoked:
groupCreated(group: Group): void {
console.log( group);
// Invoke your SignalR hub and send the details of the newly created group
}
ng-chat will generate unique ids every time a group is created so you can track which group is which whenever one gets created from a running ng-chat instance. How you will handle the persistence of these groups is up to your application.
You might want to push a notification to involved users from your SignalR adapter that their friends list has changed (They'll be able to see the group at this stage). You could also decide not to do so and only push a notification if the user who has created the group send an initial message (Once again, up to your application requirements and needs).
You might also want to implement IChatGroupAdapter on your adapter to make the contract more explicit.
Hope this helps!
I have a full engine that relies on abstractions based on user interactions. This works great with WPF/Xamarin app, cause I can implements this abstractions with window/form.
I have a little problem for porting this engine into ASP MVC.
A simple example can be show as this.
Abstraction interface (simplified)
public interface IQuestionBox
{
Task<bool> ShowYesNoQuestionBox(string message);
}
For WPF, it's really simple, I implement this interface as return the result of a window by calling ShowDialog().
In a simple business class, I can have this kind of calls (simplified) :
public async Task<string> GetValue(IQuestionBox qbox)
{
if(await qbox.ShowYesNoQuestionBox("Question ?"))
{
return "Ok";
}
return "NOk";
}
I really don't see how can I implement this kind of behavior in ASP, due to stateless of HTTP, knowing that this kind of call can be as various as domain/business need. The way I think it should be done is by returning a PartialView to inject into popup, but I don't see how to do this without breaking all the process ...
Anyone has ever done this ?
as I've said, I strongly doesn't recommend this pratice, but its possible, bellow the code that allows to do it, let's go:
To become it's possible I abused the use from TaskCompletionSource, this class allow us to set manually result in a task.
First we need to create a structure to encapsulate the process:
public class Process
{
// this dictionary store the current process running status, you will use it to define the future answer from the user interaction
private static Dictionary<string, Answare> StatusReport = new Dictionary<string, Answare>();
// this property is the secret to allow us wait for the ShowYesNoQuestion call, because til this happen the server doesn't send a response for the client.
TaskCompletionSource<bool> AwaitableResult { get; } = new TaskCompletionSource<bool>(true);
// here we have the question to interact with the user
IQuestionBox QuestionBox { get; set; }
// this method, receive your bussiness logical the receive your question as a parameter
public IQuestionBox Run(Action<IQuestionBox> action)
{
QuestionBox = new QuestionBox(this);
// here we create a task to execute your bussiness logical processment
Task.Factory.StartNew(() =>
{
action(QuestionBox);
});
// and as I said we wait the result from the processment
Task.WaitAll(AwaitableResult.Task);
// and return the question box to show the messages for the users
return QuestionBox;
}
// this method is responsable to register a question to receive future answers, as you can see, we are using our static dictionary to register them
public void RegisterForAnsware(string id)
{
if (StatusReport.ContainsKey(id))
return;
StatusReport.Add(id, new Answare()
{
});
}
// this method will deliver an answer for this correct context based on the id
public Answare GetAnsware(string id)
{
if (!StatusReport.ContainsKey(id))
return Answare.Empty;
return StatusReport[id];
}
// this method Releases the processment
public void Release()
{
AwaitableResult.SetResult(true);
}
// this method end the process delivering the response for the user
public void End(object userResponse)
{
if (!StatusReport.ContainsKey(QuestionBox.Id))
return;
StatusReport[QuestionBox.Id].UserResponse(userResponse);
}
// this method define the answer based on the user interaction, that allows the process continuing from where it left off
public static Task<object> DefineAnsware(string id, bool result)
{
if (!StatusReport.ContainsKey(id))
return Task.FromResult((object)"Success on the operation");
// here I create a taskcompletaionsource to allow get the result of the process, and send for the user, without it would be impossible to do it
TaskCompletionSource<object> completedTask = new TaskCompletionSource<object>();
StatusReport[id] = new Answare(completedTask)
{
HasAnswared = true,
Value = result
};
return completedTask.Task;
}
}
After that the question implementation
public interface IQuestionBox
{
string Id { get; }
Task<bool> ShowYesNoQuestionBox(string question);
HtmlString ShowMessage();
}
class QuestionBox : IQuestionBox
{
Process CurrentProcess { get; set; }
public string Id { get; } = Guid.NewGuid().ToString();
private string Question { get; set; }
public QuestionBox(Process currentProcess)
{
CurrentProcess = currentProcess;
CurrentProcess.RegisterForAnswer(this.Id);
}
public Task<bool> ShowYesNoQuestionBox(string question)
{
Question = question;
CurrentProcess.Release();
return AwaitForAnswer();
}
public HtmlString ShowMessage()
{
HtmlString htm = new HtmlString(
$"<script>showMessage('{Question}', '{Id}');</script>"
);
return htm;
}
private Task<bool> AwaitForAnswer()
{
TaskCompletionSource<bool> awaitableResult = new TaskCompletionSource<bool>(true);
Task.Factory.StartNew(() =>
{
while (true)
{
Thread.Sleep(2000);
var answare = CurrentProcess.GetAnswer(this.Id);
if (!answare.HasAnswered)
continue;
awaitableResult.SetResult(answare.Value);
break;
}
});
return awaitableResult.Task;
}
}
The differences for yours implementaion are:
1 - I create an Identifier to know for who I have to send the aswer, or just to stop the process.
2 - I receive a Process as parameter, because this allows us to call the method
CurrentProcess.Release(); in ShowYesNoQuestion, here in specific, releases the process to send the response responsable to interact with the user.
3 - I create the method AwaitForAnswer, here one more time we use from the TaskCompletionSource class. As you can see in this method we have a loop, this loop is responsable to wait for the user interaction, and til receive a response it doesn't release the process.
4 - I create the method ShowMessage that create a simple html script alert to simulate the user interaction.
Then a simple process class as you should be in your bussiness logical:
public class SaleService
{
public async Task<string> GetValue(IQuestionBox qbox)
{
if (await qbox.ShowYesNoQuestionBox("Do you think Edney is the big guy ?"))
{
return "I knew, Edney is the big guy";
}
return "No I disagree";
}
}
And then the class to represent the user answer
public class Answer
{
// just a sugar to represent empty responses
public static Answer Empty { get; } = new Answer { Value = true, HasAnswered = true };
public Answer()
{
}
// one more time abusing from TaskCompletionSource<object>, because with this guy we are abble to send the result from the process to the user
public Answer(TaskCompletionSource<object> completedTask)
{
CompletedTask = completedTask;
}
private TaskCompletionSource<object> CompletedTask { get; set; }
public bool Value { get; set; }
public bool HasAnswered { get; set; }
// this method as you can see, will set the result and release the task for the user
public void UserResponse(object response)
{
CompletedTask.SetResult(response);
}
}
Now we use all the entire structure create for this:
[HttpPost]
public IActionResult Index(string parametro)
{
// create your process an run it, passing what you want to do
Process process = new Process();
var question = process.Run(async (questionBox) =>
{
// we start the service
SaleService service = new SaleService();
// wait for the result
var result = await service.GetValue(questionBox);
// and close the process with the result from the process
process.End(result);
});
return View(question);
}
// here we have the method that deliver us the user response interaction
[HttpPost]
public async Task<JsonResult> Answer(bool result, string id)
{
// we define the result for an Id on the process
var response = await Process.DefineAnswer(id, result);
// get the response from process.End used bellow
// and return to the user
return Json(response);
}
and in your view
<!-- Use the question as the model page -->
#model InjetandoInteracaoComUsuario.Controllers.IQuestionBox
<form asp-controller="Home" asp-action="Index">
<!-- create a simple form with a simple button to submit the home -->
<input type="submit" name="btnDoSomething" value="All about Edney" />
</form>
<!-- in the scripts section we create the function that we call on the method ShowMessage, remember?-->
<!-- this method request the action answer passing the questionbox id, and the result from a simple confirm -->
<!-- And to finalize, it just show an alert with the process result -->
#section scripts{
<script>
function showMessage(message, id) {
var confirm = window.confirm(message);
$.post("/Home/Answer", { result: confirm, id: id }, function (e) {
alert(e);
})
}
</script>
#Model?.ShowMessage()
}
As I've said, I realy disagree with this pratices, the correct should to write a new dll, to support the web enviroment, but I hope it help you.
I put the project on github to you can download an understand all the solution
I realy hope it can help you
You can create a web socket connection from client side to server side. And work with front-end content with web socket request. It could be implemented as following:
Client side:
$app = {
uiEventsSocket : null,
initUIEventsConnection : function(url) {
//create a web socket connection
if (typeof (WebSocket) !== 'undefined') {
this.uiEventsSocket = new WebSocket(url);
} else if (typeof (MozWebSocket) !== 'undefined') {
this.uiEventsSocket = new MozWebSocket(url);
} else {
console.error('WebSockets unavailable.');
}
//notify if there is an web socket error
this.uiEventsSocket.onerror = function () {
console.error('WebSocket raised error.');
}
this.uiEventsSocket.onopen = function () {
console.log("Connection to " + url + " established");
}
//handling message from server side
this.uiEventsSocket.onmessage = function (msg) {
this._handleMessage(msg.data);
};
},
_handleMessage : function(data){
//the message should be in json format
//the next line fails if it is not
var command = JSON.parse(data);
//here is handling the request to show prompt
if (command.CommandType == 'yesNo') {
var message = command.Message;
var result = confirm(message);
//not sure that bool value will be successfully converted
this.uiEventsSocket.send(result ? "true" : "false");
}
}
}
And init it from ready or load event:
window.onload = function() { $app.initUIEventsConnection(yourUrl); }
Note that you url should begin with ws:// instead of http:// and wss:// instead of https:// (Web Sockets and Web Sockets Secure).
Server side.
Here is a good article for how to setup web sockets at asp.net core application or you could find another one. Note that you should group web socket connections from single user and if you want to send a message to the concrete user, you should send message for every connection from this user.
Every web socket you should accept with AcceptWebSocketAsync() method call and then add instance of this web socket to singleton, which contains a set of web sockets connection groupped by user.
The following class will be used to operate commands:
public class UICommand
{
public string CommandType { get; set; }
public string Message { get; set; }
public Type ReturnType { get; set; }
}
And a full code of singleton for handling sockets
public class WebSocketsSingleton
{
private static WebSocketsSingleton _instance = null;
//here stored web sockets groupped by user
//you could use user Id or another marker to exactly determine the user
private Dictionary<string, List<WebSocket>> _connectedSockets;
//for a thread-safety usage
private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim();
public static WebSocketsSingleton Instance {
get {
if (this._instance == null)
{
this._instance = new WebSocketsSingleton();
}
return this._instance;
}
}
private WebSocketsSingleton()
{
this._connectedSockets = new Dictionary<string, List<WebSocket>>();
}
/// <summary>
/// Adds a socket into the required collection
/// </summary>
public void AddSocket(string userName, WebSocket ws)
{
if (!this._connectedSockets.ContainsKey(userName))
{
Locker.EnterWriteLock();
try
{
this._connectedSockets.Add(userName, new List<WebSocket>());
}
finally
{
Locker.ExitWriteLock();
}
}
Locker.EnterWriteLock();
try
{
this._connectedSockets[userName].Add(ws);
}
finally
{
Locker.ExitWriteLock();
}
}
/// <summary>
/// Sends a UI command to required user
/// </summary>
public async Task<string> SendAsync(string userName, UICommand command)
{
if (this._connectedSockets.ContainsKey(userName))
{
var sendData = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(command));
foreach(var item in this._connectedSockets[userName])
{
try
{
await item.SendAsync(new ArraySegment<byte>(sendData), WebSocketMessageType.Text, true, CancellationToken.None);
}
catch (ObjectDisposedException)
{
//socket removed from front end side
}
}
var buffer = new ArraySegment<byte>(new byte[1024]);
var token = CancellationToken.None;
foreach(var item in this._connectedSockets[userName])
{
await Task.Run(async () => {
var tempResult = await item.ReceiveAsync(buffer, token);
//result received
token = new CancellationToken(true);
});
}
var resultStr = Encoding.Utf8.GetString(buffer.Array);
if (command.ReturnType == typeof(bool))
{
return resultStr.ToLower() == "true";
}
//other methods to convert result into required type
return resultStr;
}
return null;
}
}
Explanation:
on establishing connection from web socket it will be added with
AddSocket method
on sending request to show a message, the required command will be passed into SendAsync method
the command will be serialized to JSON (using Json.Net, however you could serialize in your way) and send to all sockets, related to the required user
after the command sent, application will wait for respond from front end side
the result will be converted to required type and sent back to your IQuestionBox
In the web socket handling your should add some kind of the following code:
app.Use(async (http, next) =>
{
if (http.WebSockets.IsWebSocketRequest)
{
var webSocket = await http.WebSockets.AcceptWebSocketAsync();
var userName = HttpContext.Current.User.Identity.Name;
WebSocketsSingleton.Instance.AddSocket(userName, webSocket);
while(webSocket.State == WebSocketState.Open)
{
//waiting till it is not closed
}
//removing this web socket from the collection
}
});
And your method implementation of ShowYesNoQuestionBox should be kind of following:
public async Task<bool> ShowYesNoQuestionBox(string userName, string text)
{
var command = new UICommand
{
CommandType = "yesNo",
Message = text,
ReturnType = typeof(bool)
};
return await WebSocketsSingleton.Instance.SendAsync(string userName, command);
}
Note that there should be added userName to prevent sending the same message to all of the connected users.
WebSocket should create the persistent connection between server and client sides, so you could simply send commands in two ways.
I am kindly new to Asp.Net Core, so the final implementation could be a bit different from this.
It's actually much the same, except your UI is sort of disconnected and proxied with the HTTP protocol for the most part.
you essentially need to build the same code as your WPF code but then in the browser construct ajax calls in to the controller actions to apply your logic.
To clarify ...
so lets say you are building up a process over a series of questions that based on the users answer you put different steps in to the process.
You can either ...
build the process in the database
build it in session on the server
build it on the client as a js object
then do a post for execution ofthe constructed process.
think of the "statelessness" as a series of short interactions, but the state you keep between them can be done either on the client, in a db or in the users logged in session on the web server.
In your controller you can add an ActionResult that will give you the html response to your jquery modal popup request. Here is an example
public class MController : Controller {
public ActionResult doWork(requirement IQuestionBox)
{
// model is already modelBound/IOC resolved
return PartialView("_doWork", requirement );
}
}
//scripts
$(function(){
$.ajax({
url:"/m/doWork",
type:"get",
success:function(data){
$modal.html(data); // bind to modal
}
});
});
Apologies for not fully understanding the question.
hope this helps!
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}.");
}
Follow my code (just the demo doubt), this code I have a method in my service that receives a list of users and saves, the only rule of business is to check if the User already exists.
There is also a log of that call, the idea is simple it will work quiet, My doubt is, I'm doing 2 SaveChanges () and it seems to be wrong, how could I improve this code? I thought I would call only once in CreateUser method (), but that would be dangerous, because there could be repetitions of CPF in the "Users" list, I would have to create a validate this list, but wanted to avoid this, as it seems to me least of the problems, I think I'm erring on the solution architecture.
public class Service : IMyService {
private UserEntitiescontext context = new UserEntities();
// Service method
public bool CreateUser(List<User> users) {
foreach (var user in users) {
new UserDomain().createUser(context, user);
new LogDomain().createLog(context, new Log { UserCreated = user .... });
}
}
}
public class UserDomain() {
private createUser(UserEntities context, User user) {
if (context.Users.Where(f=>f.CPF == user.CPF).FirstOrDefault() != null) {
context.Attach(user);
context.SaveChanges();
}
}
}
public class LogDomain() {
private createLog(UserEntities context, Log log) {
context.Attach(log);
context.SaveChanges();
}
}
public bool CreateUser(List<User> users) {
foreach (var user in users) {
new UserDomain().createUser(context, user);
new LogDomain().createLog(context, new Log { UserCreated = user .... });
}
context.SaveChanges();
}
Not solve because I can have the list of "users" duplication of CPF. Besides, that way, my class "UserDomain" would be having more of a liability, wounding patterns.