SignalR: Server broadcast without persisting notifications - asp.net

I would like to notify online users that a new chat room has been created. I don't need to notify offline users once they come online.
I looked at AbpNotifications but this seems to persist notifications and notify a user once they come online. I only want to notify currently online users.
I looked at SignalR integration but could not find a way for the server to initiate a message. For instance, I might want the ApplicationService.ChatRoom.Create method to initiate the message.
I did find documentation for how to get SignalR (outside of ABP) to initiate a message: https://learn.microsoft.com/en-us/aspnet/signalr/overview/getting-started/tutorial-server-broadcast-with-signalr#server

Inject and use IRealTimeNotifier.
private async Task SendRealTimeNotificationsAsync(string message, UserIdentifier[] users)
{
var data = new MessageNotificationData(message);
var notification = new TenantNotification { Data = data };
var userNotifications = users.Select(user => new UserNotification
{
TenantId = user.TenantId,
UserId = user.UserId,
Notification = notification
});
await RealTimeNotifier.SendNotificationsAsync(userNotifications.ToArray());
}
More info:
https://aspnetboilerplate.com/Pages/Documents/Notification-System#real-time-notifications

Related

How can I ensure an API call response completes before an operation in Blazor WASM

I've scoured stackoverflow looking for ways to make synchronous API calls in Blazor WASM, and come up empty. The rest is a fairly length explanation of why I think I want to achieve this, but since Blazor WASM runs single-threaded, all of the ways I can find to achieve this are out of scope. If I've missed something or someone spots a better approach, I sincerely appreciate the effort to read through the rest of this...
I'm working on a Blazor WASM application that targets a GraphQL endpoint. Access to the GraphQL endpoint is granted by passing an appropriate Authorization JWT which has to be refreshed at least every 30 minutes from a login API. I'm using a 3rd party GraphQL library (strawberry-shake) which utilizes the singleton pattern to wrap an HttpClient that is used to make all of the calls to the GraphQL endpoint. I can configure the HttpClient using code like this:
builder.Services
.AddFxClient() // strawberry-shake client
.ConfigureHttpClient((sp, client) =>
{
client.BaseAddress =
new Uri(
"https://[application url]/graphql"); // GraphQL endpoint
var token = "[api token]"; // token retrieved from login API
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
});
The trick now is getting the API token from the login API at least every 30 minutes. To accomplish this, I created a service that tracks the age of the token and gets a new token from the login API when necessary. Pared down, the essential bits of the code to get a token look like this:
public async Task<string> GetAccessToken()
{
if ((_expirationDateTime ?? DateTime.Now).AddSeconds(-300) < DateTime.Now)
{
try
{
var jwt = new
{
token =
"[custom JWT for login API validation]"
};
var payload = JsonSerializer.Serialize(jwt);
var content = new StringContent(payload, Encoding.UTF8, "application/json");
var postResponse = await _httpClient.PostAsync("https://[login API url]/login", content);
var responseString = await postResponse.Content.ReadAsStringAsync();
_accessToken = JsonSerializer.Deserialize<AuthenticationResponse>(responseString).access_token;
_expirationDateTime = DateTime.Now.AddSeconds(1800);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
return _accessToken;
}
So, now I need to wire this up to the code which configures the HttpClient used by the GraphQL service. This is where I'm running into trouble. I started with code that looks like this:
// Add login service
builder.Services.AddSingleton<FxAuthClient>();
// Wire up GraphQL client
builder.Services
.AddFxClient()
.ConfigureHttpClient(async (sp, client) =>
{
client.BaseAddress =
new Uri(
"https://[application url]/graphql");
var token = await sp.GetRequiredService<FxAuthClient>().GetAccessToken();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
});
This "works" when the application is loaded [somewhat surprisingly, since notice I'm not "await"ing the GetAccessToken()]. But the behavior if I let the 30 minute timer run out is that the first attempt I make to access the GraphQL endpoint uses the expired token and not the new token. I can see that GetAccessToken() refreshes expired token properly, and is getting called every time I utilize the FxClient, but except for the first usage of FxClient, the GetAccessToken() code actually runs after the GraphQL request. So in essence, it always uses the previous token.
I can't seem to find anyway to ensure that GetAccessToken() happens first, since in Blazor WASM you are confined to a single thread, so all of the normal ways of enforcing synchronous behavior fails, and there isn't an asynchronous way to configure the FxClient's HttpClient.
Can anyone see a way to get this to work? I'm thinking I may need to resort to writing a wrapper around the strawberry FxClient, or perhaps an asynchronous extension method that wraps the ConfigureHttpClient() function, but so far I've tried to avoid this [mostly because I kept feeling like there must be an "easier" way to do this]. I'm wondering if anyone knows away to force synchronous behavior of the call to the login API in Blazor WASM, sees another approach that would work, or can offer any other suggestion?
Lastly, it occurs to me that it might be useful to see a little more detail of the ConfigureHttpClient method. It is autogenerated, so I can't really change it, but here it is:
public static IClientBuilder<T> ConfigureHttpClient<T>(
this IClientBuilder<T> clientBuilder,
Action<IServiceProvider, HttpClient> configureClient,
Action<IHttpClientBuilder>? configureClientBuilder = null)
where T : IStoreAccessor
{
if (clientBuilder == null)
{
throw new ArgumentNullException(nameof(clientBuilder));
}
if (configureClient == null)
{
throw new ArgumentNullException(nameof(configureClient));
}
IHttpClientBuilder builder = clientBuilder.Services
.AddHttpClient(clientBuilder.ClientName, (sp, client) =>
{
client.DefaultRequestHeaders.UserAgent.Add(
new ProductInfoHeaderValue(
new ProductHeaderValue(
_userAgentName,
_userAgentVersion)));
configureClient(sp, client);
});
configureClientBuilder?.Invoke(builder);
return clientBuilder;
}

How to schedule and send the message in MS teams bot

I have created a teams bot and had a service written in .NET core to handle the user's messages to reply accordingly. However I wanted to schedule a message and send it to the user(i.e. initiate conversation from bot).
I have gone through the online available sources, most of which refer the documentation for sending proactive message. But it didn't help as in my scenario I want to initiate a conversation on a specific time of the day and I am not getting in which event handler I can write the code.
I also tried Azure functions as it can be scheduled and try to write code for sending message in teams, but I got some package related errors which I am not able to resolve.
I am looking for a solution in the service I already have, if possible. Nonetheless anything will work.
A sample of code will be very helpful, as I am not much experienced in bot programming.
Thanks in advance.
I think my experience will help you. Some days before I got a request to make Teams bot send proactive message to a chat group at a specific time. My idea was creating an Api in my code and setting a time trigger to make my function call this Api. And it really worked.
Here's my function code, and you can know about how to set time trigger in this document:
#r "Newtonsoft.Json"
using System.Net;
using System.IO;
using System.IO.Compression;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
public static void Run(TimerInfo myTimer, ILogger log)
{
log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://xxxteamsbot.azurewebsites.net/api/sendProactiveMesg");
request.Method = "GET";
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Stream myResponseStream = response.GetResponseStream();
}
Here's my Controller:
[Route("api/sendProactiveMesg")]
[ApiController]
public class ProactiveController : Controller
{
public async Task sendProactiveMesg()
{
ProactiveMesgChannel a = new ProactiveMesgChannel();
await a.sendtoGroupChat();
//ProactiveMesgPersonal a = new ProactiveMesgPersonal();
//await a.sendtoPersonal();
}
}
Here's my proactive message code:
You can get groupChatConversationId and serviceUrl by using
Filder to catch the request detail when your target message receiver
is chatting with the bot.
BotClientId and BotClientSecret are from Azure ad application.
public async Task sendtoGroupChat()
{
string groupChatConversationId = "19:5~~~f72c#thread.v2";
string serviceUrl = "https://s~~~t/amer/";
string botClientID = "e~~~c";
string botClientSecret = "5z~~~A";
AppCredentials.TrustServiceUrl(serviceUrl);
ConnectorClient connectorClient = new ConnectorClient(new Uri(serviceUrl), new MicrosoftAppCredentials(botClientID, botClientSecret), true);
IMessageActivity message = await showTeamStatus();
await connectorClient.Conversations.SendToConversationAsync(groupChatConversationId, (Activity)message);
}
private async Task<IMessageActivity> showTeamStatus()
{
GetAnswersDetail detail = new GetAnswersDetail();
List<ResData> res = await detail.GetDetailByIds();
HeroCard card = new HeroCard();
card.Title = "Status In " + getMonth();
string html = "<div>Here is the detail view";
html = (card.Text = html + "</div> ");
return MessageFactory.Attachment(card.ToAttachment());
}
By the way, if you are new to Teams conversation bot program, I think my another answer will help you to create a bot. And to know about how to send proactive message, watch this document.

Bi-Directional Communication via IoTHub/Xamarin App/ESP8266

Working on a new product at work that will be using an ESP8266, Xamarin app, and the Azure IoTHub to enable bidirectional communication for customer's devices.
We've got C2D (Cloud 2 Device) and D2C (Device 2 Cloud) communication working properly on both the app and the ESP, but we are not finding any information on setting up the IoTHub to interpret incoming Telemetry messages, process their respective "To:" field and put them back in to the C2D topic, which should allow our target device to receive it.
What we have tried:
Logic Apps. Were able to trigger on incoming messages to the queue, but not sure what HTTP request to do in order to forward it back in to the C2D event hub.
We have successfully been able to forward each message in to a queue, but the PCL library for Xamarin is not capable of connecting to Azure Service Bus Queues (bummer).
I found a reference for an intern at Microsoft developing direct device to device communication for a garage door opener, but the library she is using is only available for UWP apps, which isn't all that convenient, when we really want to target iOS, Android and UWP (reason for choosing Xamarin in the first place).
https://blogs.windows.com/buildingapps/2016/09/08/device-to-device-communication-with-azure-iot-hub/#ykPJrVE734GpSEzV.97
Has anyone been able to trigger C2D conditional events using the Azure portal?
Through some conversations with Microsoft Azure team, we determined that a webjob combined with a route to a queue was the best solution for us.
All messages are routed to the queue and as they arrive in the queue, the webjob processes the message and sends the message on using a ServiceBus Messaging object to send the cloud to device response message.
Here's the code for anyone who wants to use it.
As long as the original sender of the message specifies the "To" property in the brokered message, it will be delivered to that device in the registry. You will need the Service Bus and Azure.Messaging NuGet packages in order to use this. This code will copy the entire message and send the whole thing to the desired registry device.
private const string queueName = "<queue_name>";
private const string IoTHubConnectionString = "HostName=<your_host>;SharedAccessKeyName=<your_service_user>;SharedAccessKey=<your sas>";
// This function will get triggered/executed when a new message is written
// on an Azure Queue called <queue_name>.
public static void ReceiveQueueMessages(
[ServiceBusTrigger(queueName)] BrokeredMessage message,
TextWriter log)
{
if (message.To == null)
{
//message = null
return;
}
else
{
//Retrieve the message body regardless of the content as a stream
Stream stream = message.GetBody<Stream>();
StreamReader reader;
if (stream != null)
reader = new StreamReader(stream);
else
reader = null;
string s;
Message serviceMessage;
if ( reader != null )
{
s = reader.ReadToEnd();
serviceMessage = new Microsoft.Azure.Devices.Message(Encoding.ASCII.GetBytes(s));
}
else
{
serviceMessage = new Microsoft.Azure.Devices.Message();
}
foreach (KeyValuePair<string, object> property in message.Properties)
{
serviceMessage.Properties.Add(property.Key, property.Value.ToString());
}
SendToIoTHub(message.To.ToString(), serviceMessage);
}
}
static async void SendToIoTHub(string target, Microsoft.Azure.Devices.Message message)
{
// Write it back out to the target device
ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(IoTHubConnectionString);
var serviceMessage = message;
serviceMessage.Ack = DeliveryAcknowledgement.Full;
serviceMessage.MessageId = Guid.NewGuid().ToString();
try
{
await serviceClient.SendAsync(target, serviceMessage);
}
catch
{
await serviceClient.CloseAsync();
return;
}
await serviceClient.CloseAsync();
}

Using SignalR to send message to client from Azure Worker Role

I'm working an ASP.net MVC cloud service project running two roles, a web role and a worker role. One of the pages in the web role initiate a request to build an APK file, building an APK file on the server can take anywhere from 1-5 minutes. So we came up with the following flow:
The user initiate the APK building process on the page.
The request is routed to our mvc action, creating a new message on an Azure Storage Queue.
The Worker role is always polling from the queue and starts the APK building process. Now that the APK is ready we want ideally to notify the user by:
(a) sending an email, which is working now. and (b) notifying the user on the page using SignalR.
Our problem is now in the SignalR part, how can we notify the user on the page that the APK is ready and he can download it.
EDIT - Copying contents of the first comment for the sake of completeness -
I've looked the question again and I understand that you are using a worker role to poll the queue. In this case, you can make your work role a .Net SignalR client that connects to the APK signalR hub on the web role. The signlaR hub on the web role can simple forward any message it receives from the .Net client to the javascript client (browser).
I would recommend going through the below links
Hubs API Guide - Server
Hubs API Guide - Javascript Client
before going through rest of the answer.
As can be understood from the above two links, SignalR enables the server to 'push' data to the client. In order for this to happen, you require two things -
A signalR hub - this is the 'hub' to which clients can subscribe to in order to receive messages.
A client connected to the hub
Your signalR hub on the server can look something like this -
public class APKHub : Hub
{
public async Task JoinGroup(string groupName)
{
await Groups.Add(Context.ConnectionId, groupName);
Clients.Group(groupName).sendMessage(Context.User.Identity.Name + " joined.");
}
public Task LeaveGroup(string groupName)
{
return Groups.Remove(Context.ConnectionId, groupName);
}
public void NotifyUser(string userId)
{
this.Clients.Group(userId).notify();
}
}
On the client, your code might look something like this -
var notificationHandler = function () {
var url;
var user;
var init = function (notificationUrl, userId) {
url = notificationUrl;
user = userId;
connectToAPKHub();
}
var connectToAPKHub = function () {
$.connection.hub.url = url;
var apk= $.connection.apkHub;
apk.client.notifyUser = function (user) {
console.log(user);
}
apk.client.addMessage = function (message) {
console.log(message);
}
$.connection.hub.start().done(function () {
console.log('connected to apkhub');
apk.server.joinGroup(user);
})
}
return {
init: init
}
}();
The notificationUrl is the URL that the signalR server is listening to.
This sets up your basic hub on the server and you should now be able to connect your client to the signalR hub. When the APK is built, you can use the following code (place it anywhere - for ex - in a controller action) to actually push a message to the concerned client -
var apkHub = GlobalHost.ConnectionManager.GetHubContext<APKHub>();
apkHub.Clients.Group(groupName).notifyUser(groupName);
The groupName can be an identifier that uniquely identifies a user.
Hope this helps.

Use Signalr to have a facebook like notification system

I want to implement a facebook like notification system in ASP.NET MVC 3 : notifications are sent to a specific user to notify him for an action on one of his items.
Is signalr suited for such requirement?
How could i send a notification to a specific user (all opened sessions of this user) using SignalR?
Edit
Ok, Here what i did
In the client side
$(function () {
// Proxy created on the fly
var chat = $.connection.chat;
var username = '#Html.ViewContext.HttpContext.User.Identity.Name';
// Declare a function on the chat hub so the server can invoke it
chat.addMessage = function (message) {
$('#messages').append('<li>' + message + '</li>');
};
// Start the connection
$.connection.hub.start(function (){
chat.join(username);
});
});
In the server side
public class Chat : Hub
{
public void Join(string username)
{
AddToGroup(username);
}
}
And every time i need to notify a user in the controller i do the following:
IConnectionManager connectionManager = AspNetHost.DependencyResolver.Resolve<IConnectionManager>();
dynamic clients = connectionManager.GetClients<Chat>();
clients[username].addMessage("test");
Yes, SignalR is a good choice for that. Take a look at the documentation regarding Hubs (server and JS client).
You need to implement the server logic to associate your client's session with SignalR's session. You can use groups to notify all the open sessions of each user.
It is appropriate for this or you use polling, those are the two choices.
Heres a brand new video from today on this:
http://channel9.msdn.com/Shows/Web+Camps+TV/Damian-Edwards-and-David-Fowler-Demonstrate-SignalR?utm_source=dlvr.it&utm_medium=twitter

Resources