I want to send real-time notification in ASP.NET Boilerplate. Notification is saving successfully in Abp.NotificationSubscription table on subscription. When I publish the notification, the notification got saved in the Abp.Notification table but it is not displayed to the user in real-time.
My server-side code:
public async Task<RegisterOutput> Register(RegisterInput input)
{
public async Task<RegisterOutput> Register(RegisterInput input)
{
var user = await _userRegistrationManager.RegisterAsync(
input.Name,
input.Surname,
input.EmailAddress,
input.UserName,
input.Password,
true
);
_notificationSubscriptionManager.SubscribeToAllAvailableNotifications(user.ToUserIdentifier());
await _appNotifier.WelcomeToTheApplicationAsync(user);
var notification = _userNotificationManager.GetUserNotifications(user.ToUserIdentifier());
await _realTimeNotifier.SendNotificationsAsync(notification.ToArray());
// ...
}
}
_appNotifier implements WelcomeToTheApplicationAsync(user):
public async Task WelcomeToTheApplicationAsync(User user)
{
await _notificationPublisher.PublishAsync(
AppNotificationName.WelcomeToTheApplication,
new SendNotificationData("Naeem", "Hello I have sended this notification to you"),
severity: NotificationSeverity.Success,
userIds: new[] { user.ToUserIdentifier() }
);
}
SendNotificationData inherits from NotificationData:
public class SendNotificationData : NotificationData
{
public string name { get; set; }
public string message { get; set; }
public SendNotificationData(string _name, string _message)
{
name = _name;
message = _message;
}
}
I want to sent welcome notification to the user when he registers itself by using the register link on the login page. In the CloudbaseLine.Application project ... we have AccountAppService which contain the code for registering the user and sending notification to it after successful registration but the problem is notification go saved in Abp.NotificationSubscriptionTable and UserNotificationTable but user cannot received them.
public async Task<RegisterOutput> Register(RegisterInput input)
{
var user = await _userRegistrationManager.RegisterAsync(
input.Name,
input.Surname,
input.EmailAddress,
input.UserName,
input.Password,
true
);
_notificationSubscriptionManager.SubscribeToAllAvailableNotifications(user.ToUserIdentifier());
await _appNotifier.WelcomeToTheApplicationAsync(user);
var notification = _userNotificationManager.GetUserNotifications(user.ToUserIdentifier());
await _realTimeNotifier.SendNotificationsAsync(notification.ToArray());
// ...
}
public async Task WelcomeToTheApplicationAsync(User user)
{
await _notificationPublisher.PublishAsync(
AppNotificationName.WelcomeToTheApplication,
new SendNotificationData("Naeem", "Hello I have sended this notification to you"),
severity: NotificationSeverity.Success,
userIds: new[] { user.ToUserIdentifier() }
);
}
The user is not connected to the SignalR hub in the Register method.
One way to handle that is to enqueue a background job:
await _backgroundJobManager.EnqueueAsync<WelcomeNotificationJob, UserIdentifier>(
user.ToUserIdentifier(),
delay: TimeSpan.FromSeconds(5)
);
public class WelcomeNotificationJob : BackgroundJob<UserIdentifier>, ITransientDependency
{
private readonly IRealTimeNotifier _realTimeNotifier;
private readonly IUserNotificationManager _userNotificationManager;
public WelcomeNotificationJob(
IRealTimeNotifier realTimeNotifier,
IUserNotificationManager userNotificationManager)
{
_realTimeNotifier = realTimeNotifier;
_userNotificationManager = userNotificationManager;
}
[UnitOfWork]
public override void Execute(UserIdentifier args)
{
var notifications = _userNotificationManager.GetUserNotifications(args);
AsyncHelper.RunSync(() => _realTimeNotifier.SendNotificationsAsync(notifications.ToArray()));
}
}
Don't forget to register data formatters for custom notification data types, on the client side:
abp.notifications.messageFormatters['CloudBaseLine.Notification.SendNotificationData'] = function (userNotification) {
return userNotification.notification.data.message;
}
Related
I have created a chat using SignalR2. The client and server itself works fine. Now, I'm trying to implement a 'users online' function. The server code seems about right, but I'm struggling to make the client receive the data that the server pushes back to the client.
This is the server code below:
public static List<string> Users = new List<string>();
public void Send(string name, string message)
{
// Call the broadcastMessage method to update clients.
Clients.All.broadcastMessage(name, message);
Clients.All.addMessage(name, message);
}
public void SendUserList(List<string> users)
{
var context = GlobalHost.ConnectionManager.GetHubContext<chatHub>();
context.Clients.All.updateUserList(users);
}
public override Task OnConnected()
{
string clientId = GetClientId();
//if (Users.IndexOf(clientId) == -1)
//{
Users.Add(clientId);
//}
SendCount(Users.Count);
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
System.Diagnostics.Debug.WriteLine("Disconnected");
SendCount(Users.Count);
return base.OnDisconnected(stopCalled);
}
private string GetClientId()
{
string clientId = "";
if (Context.QueryString["clientId"] != null)
{
// clientId passed from application
clientId = this.Context.QueryString["clientId"];
}
if (string.IsNullOrEmpty(clientId.Trim()))
{
clientId = Context.ConnectionId;
}
return clientId;
}
public void SendCount(int count)
{
// Call the addNewMessageToPage method to update clients.
var context = GlobalHost.ConnectionManager.GetHubContext<chatHub>();
context.Clients.All.updateUsersOnlineCount(count);
}
Below is the client code for connecting / receiving messages:
public static async void ConnectAsync(RadChat ChatInternal)
{
ChatInternal.Author = new Author(null, Varribles.Agent);
var querystringData = new Dictionary<string, string>();
querystringData.Add("clientId", Varribles.Agent);
Connection = new HubConnection(ServerURI, querystringData);
HubProxy = Connection.CreateHubProxy("chatHub");
//Handle incoming event from server: use Invoke to write to console from SignalR's thread
HubProxy.On<string, string>("AddMessage", (name, message) =>
ChatInternal.Invoke((Action)(() =>
Backend.GET.Messages(ChatInternal)
)));
try
{
await Connection.Start();
Backend.GET.Messages(ChatInternal);
}
catch (System.Net.Http.HttpRequestException)
{
//No connection: Don't enable Send button or show chat UI
return;
}
}
Now, my question is, how can I retrieve the 'Users' list from the server?
Thanks in advance
SignalR core is very new, so the docs detail how to use about it are very rare.
I've done the tutorial from Microsoft and successfully sent messages to all the clients. Now I want to send for specific user, with
public Task SendPrivateMessage(string user, string message, string to)
{
return Clients.User(to).SendAsync("ReceiveMessage", user, message);
}
the "to" value is the ConnectionID I got from the
public override Task OnConnectedAsync()
{
Console.WriteLine("New ID Connected: " + Context.ConnectionId);
return base.OnConnectedAsync();
}
Here is my client:
public async void InitSignalRAsync()
{
ChatMessage mess = new ChatMessage();
hubConnection = new HubConnectionBuilder().WithUrl("http://localhost:5000/chatHub").Build();
await hubConnection.StartAsync();
hubConnection.On<string, string>("ReceiveMessage", async (user, message) =>
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
mess.user = user;
mess.message = message;
Messages.Add(mess);
});
});
}
private void Send_Click(object sender, RoutedEventArgs e)
{
hubConnection.InvokeAsync("SendPrivateMessage", User.Text, Text.Text, To.Text);
}
The console logs nothing error so I guess it did send but why can't I receive it?
For send to client by client's connectionId you should use of Clients.Client(coonectionId)
,Clients.User() is for send to uniqe user by user's Id.to do send message by user id you can try as follows:
-create a CustomUserIdProvider:
public class CustomUserIdProvider: IUserIdProvider
{
public virtual string GetUserId(HubConnectionContext connection)
{
//get current user id by httpcontext
}
}
and then in startup.cs:
services.AddSignalR();
services.AddSignalRCore();
services.AddSingleton<IUserIdProvider, CustomUserIdProvider>();
now in your hub you can send message by user id:
public void Send(string userId, string message)
{
Clients.User(userId).send(message);
}
for more info go to this link
I am working on a multi dialog form flow chat bot using bot framework.
Below is one of my dialog code where I want to get the confirmation from the user and if necessary, needs to alter customer selection/ parameters provided.
below is the code I'm working on
Dialog
namespace FormBot.Dialogs
{
[Serializable]
public class HardwareDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync("Welcome to the Hardware solution helpdesk!");
var HardwareFormDialog = FormDialog.FromForm(this.BuildHardwareForm, FormOptions.PromptInStart);
context.Call(HardwareFormDialog, this.ResumeAfterHardwareFormDialog);
}
private IForm<HardwareQuery> BuildHardwareForm()
{
OnCompletionAsyncDelegate<HardwareQuery> HardwareRequest = async (context, state) =>
{
string message = string.Empty;
await context.PostAsync($"Ok. {message}. Once we resolve it; we will get back to you....");
};
return new FormBuilder<HardwareQuery>()
.Field(nameof(HardwareQuery.Hardware))
.Message($"We are Creating Ticket your request ...")
.AddRemainingFields()
.OnCompletion(HardwareRequest)
.Build();
}
private async Task ResumeAfterHardwareFormDialog(IDialogContext context, IAwaitable<HardwareQuery> result)
{
try
{
}
catch (FormCanceledException ex)
{
string reply;
if (ex.InnerException == null)
{
reply = "You have canceled the operation. Quitting from the HardwareDialog";
}
else
{
reply = $"Oops! Something went wrong :( Technical Details: {ex.InnerException.Message}";
}
await context.PostAsync(reply);
}
finally
{
context.Done<object>(null);
}
}
public static IForm<HardwareDialog> BuildForm()
{
return new FormBuilder<HardwareDialog>()
.Message("Welcome to Service Ticket Bot!")
.Build();
}
}
}
Query builder
public enum HardwareOptions
{
Desktop, KeyBoard, Laptop, Monitor, Mouse, Printer, Scanner, Server, Tablet
};
[Serializable]
public class HardwareQuery
{
[Prompt("Choose your {&} ? {||}")]
public HardwareOptions? Hardware;
[Prompt("Please enter {&}")]
[Pattern(Utility.Phone)]
public string PhoneNumber { get; set; }
[Prompt("Please enter {&} ")]
[Pattern(Utility.Email)]
public string Email { get; set; }
[Prompt("Please provide your business need / {&} below")]
public string Justification { get; set; }
public static IForm<HardwareQuery> BuildForm()
{
OnCompletionAsyncDelegate<ServiceTicket> processOrder = async (context, state) =>
{
await context.PostAsync($"Once we resolve it; we will get back to you....");
};
return new FormBuilder<HardwareQuery>()
.Message("Welcome !")
.Build();
}
}
}
Expected result
Asking for confirmation
Updating the result set
To ask for a confirmation of the selected values in FormFlow, you could use the .Confirm() while building the form. In case you want to prompt for a customized message, you could use .Confirm("Do you confirm the chosen hardware - {Hardware} and your email - {Email} and phone number {PhoneNumber}"). For detailed approaches, have a look at the official documentation. Based on the choice of the confirmation, bot framework's form flow will automatically handle the posting in the questions to change and updating the results.
P.S. On a side note, I am not sure why you are trying to use FormDialog.FromForm and the other unnecessary code, you could simplify the HardwareDialog as follows:
namespace FormBot.Dialogs
{
[Serializable]
public class HardwareDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync("Welcome to the Hardware solution helpdesk!");
var HardwareFormDialog = new FormDialog<HardwareQuery>(new HardwareQuery(), HardwareQuery.BuildForm, FormOptions.PromptInStart);
context.Call(HardwareFormDialog, this.ResumeAfterHardwareFormDialog);
}
private async Task ResumeAfterHardwareFormDialog(IDialogContext context, IAwaitable<HardwareQuery> result)
{
try
{
}
catch (FormCanceledException ex)
{
string reply;
if (ex.InnerException == null)
{
reply = "You have canceled the operation. Quitting from the HardwareDialog";
}
else
{
reply = $"Oops! Something went wrong :( Technical Details: {ex.InnerException.Message}";
}
await context.PostAsync(reply);
}
finally
{
context.Done<object>(null);
}
}
I have implement a bot using Microsoft Bot builder SDK v-4 (pre-release). To manage the conversation flow I have used two simple dialogs-
GreetingDialog - DialogBegin: To greet the user first time
public Task DialogBegin(DialogContext dc, IDictionary<string, object> dialogArgs = null)
{
var state = dc.Context.GetConversationState<EchoState>();
string greetMessage = string.Format("Hi, I am {0}.", _botName);
dc.Context.SendActivity(greetMessage);
IList<CardAction> suggestedActions = new List<CardAction>
{
//some card action suggestions
};
var activity = MessageFactory.SuggestedActions(suggestedActions, text: "Please select the area of conversation.");
dc.Context.SendActivity(activity);
dc.End();
return Task.CompletedTask;
}
ConversationDialog - DialogBegin: To continue the subsequent conversation after the user has been greeted
public Task DialogBegin(DialogContext dc, IDictionary<string, object> dialogArgs = null)
{
string activity = "test";
dc.Context.SendActivity(activity);
dc.Continue();
return Task.CompletedTask;
}
I am calling the GreetingDialog in the ConversationUpdate event and the ConversationDialog in the subsequent message received event, within the OnTurn method in my Bot class.
OnTurn event in my Bot class:
public async Task OnTurn(ITurnContext context)
{
var state = context.GetConversationState<EchoState>();
var dialogCtx = _dialogs.CreateContext(context, state);
if (context.Activity.Type == ActivityTypes.ConversationUpdate)
{
//Greet user first time
if (context.Activity.MembersAdded[0].Id == "default-user")
{
return;
}
if (!context.Responded)
{
var args = new Dictionary<string, object>
{
["greetingArgs"] = context.Activity.Text
};
await dialogCtx.Begin("greetingDialog", args);
}
}
else if (context.Activity.Type == ActivityTypes.Message)
{
await dialogCtx.Continue(); //this line is supposed to execute Begin the active dialog again??
//if (!context.Responded)
if(dialogCtx.ActiveDialog == null || !dialogCtx.Context.Responded)
{
var args = new Dictionary<string, object>
{
["conversationArgs"] = context.Activity.Text
};
await dialogCtx.Begin("conversationDialog", args);
}
}
}
Using the above code, I get redirected to ConversationDialog but it only happens through await dialogCtx.Begin("conversationDialog", args);. Isn't it supposed to redirect to DialogBegin of the Active dialog when I do await dialogCtx.Continue();? I can see the Active dialog is 'conversationDialog' and the debugger steps over through await dialogCtx.Continue();. Any help with this please?
I think I figured it out. We can implement the IDialogContinue interface for our Dialog class like this-
public class QnADialog : IDialog, IDialogContinue
{
public Task DialogBegin(DialogContext dc, IDictionary<string, object> dialogArgs = null)
{
string activity = "test";
dc.Context.SendActivity(activity);
//dc.Continue();
return Task.CompletedTask;
}
public Task DialogContinue(DialogContext dc)
{
dc.Context.SendActivity("dc continue");
dc.Context.Responded = true;
return Task.CompletedTask;
}
}
Then we can use the DialogContinue method to handle the DialogContext.Continue() from the calling code.
I have application in which I am showing data from sensors using SignalR. It uses ASP.net membership to authenticate the users. It all works fine if I only open one browser window(e.g. Firefox). If I open same website in another browser e.g. Chrome at the same time then signalR connection to firefox browser drops even if the user is different. This is what I am using to broadcast message:
Hub
[Authorize]
public class DataHub:Hub
{
private readonly RealTimeData _sensor;
public DataHub() : this(RealTimeData.Instance) { }
public DataHub(RealTimeData data)
{
_sensor = data;
}
public override Task OnConnected()
{
// _sensor.UserId = Context.ConnectionId; changed to
_sensor.UserId = Membership.GetUser().ProviderUserKey.ToString();
return base.OnConnected();
}
}
public class RealTimeData
{
//User Id
public String UserId { get; set; }
private readonly static Lazy<RealTimeData> _instance = new Lazy<RealTimeData>(() => new RealTimeData(GlobalHost.ConnectionManager.GetHubContext<DataHub>().Clients));// Singleton instance
private IHubConnectionContext Clients;
private void BroadcastDataOfAllSensors(List<SensorDetails> sensor)
{
//Clients.Client(UserId).updateDashboard(sensor);changed to
Clients.User(UserId).updateDashboard(sensor);
}
}
Application Startup
public class StartUp
{
public void Configuration(IAppBuilder app)
{
var idProvider = new UserIdProvider();
GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => idProvider);
app.MapSignalR();
}
}
UserId
public class UserIdProvider : IUserIdProvider
{
public string GetUserId(IRequest request)
{
var userId = Membership.GetUser().ProviderUserKey;
return userId.ToString();
}
}