I have a Blazor app written in .NET6 which implements SignalR.
Here is an example of SignalR Hub in server side:
[HubName("ThreadHub")]
public class ThreadHub : Hub
{
public async Task SendMessage(Threading_Pair threading_Pair)
{
await Clients.All.SendAsync("ReceiveMessage", threading_Pair);
}
}
Here is an example of maphub in program.cs in server side:
app.MapHub<ThreadHub>("/threadhub");
Here is an example of SignalR initialization on razor component in client side:
private HubConnection? hubConnection;
private IList<string> messages = new List<string>();
protected override async Task OnInitializedAsync()
{
// Init Broadcast service with SignalR
hubConnection = new HubConnectionBuilder()
.WithUrl(navigationManager.ToAbsoluteUri("/threadhub"))
.Build();
hubConnection.On<string, string> ("ReceiveMessage", (id, message) => {
var encodedMsg = $"{id}: {message}";
messages.Add(encodedMsg);
StateHasChanged();
});
await hubConnection.StartAsync();
}
Here is an example of SignalR function to send message on razor component in client side:
if (hubConnection is not null)
{
if (hubConnection.State == HubConnectionState.Connected)
{
await hubConnection.SendAsync("SendMessage", "Admin", "Hellow to all users! Starting the heavy job!");
}
}
The application now is working fine between client and server and can send and retrieve messages across all open windows.
The question is how the application itself can send messages from server side?
For example this application is generating some threads and i want to know start and stop of their process. In this case the call will happen from the server side, so how i can efficiently sent message from server?
So far the only though is to open a hubConnection like in the razor component in client side"
hubConnection = new HubConnectionBuilder()
.WithUrl(navigationManager.ToAbsoluteUri("/threadhub"))
.Build();
In the above example the navigationManager.ToAbsoluteUri() translates for my my running url application in the client and adds the "/threadhub"
but in this case, I do not have the Navigation manager to get the url of the application, any ideas?
Related
I am very new with orleans and trying to grasp everything with grains and so forth.
What i got is that in my startup.cs file i add the SignalR like this
public IServiceProvider ConfigureServices(IServiceCollection services)
{
Program.WriteConsole("Adding singletons");
services
.AddSingleton(achievementManager)
.AddMvc();
services.AddSingleton(SignalRClient);
return services.BuildServiceProvider();
}
So far everything is fine i can start my host/application and it connects to SignalR as it should. But what i cant wrap my head around is how do i get this down to my grain? if i had a controller i would simply send it down in the constructor on startup but how do i do this with a grain? Or can i even do it like this. Any guidance is appreciated.
In the grain then i want to do something like this
[StatelessWorker]
[Reentrant]
public class NotifierGrain : Grain, INotifierGrain
{
private HubConnection SignalRClient { get; }
public NotifierGrain(HubConnection signalRClient)
{
SignalRClient = signalRClient;
SignalRClient.SendAsync(Methods.RegisterService, Constants.ServiceName);
}
public Task NotifyClients(object message, MessageType type)
{
var registerUserNotification = (RegisterUserNotificationModel)message;
SignalRClient.SendAsync(Methods.RegisterUserToMultipleGroups, registerUserNotification.UserId, registerUserNotification.InfoIds);
}
return Task.CompletedTask;
}
Then i try to call the Notify method from another grain like this
var notifier = GrainFactory.GetGrain<INotifierGrain>(Constants.NotifierGrain);
await notifier.NotifyClients(notification, MessageType.RegisterUser);
But trying to do this ends up with an error like this
InvalidOperationException: Unable to resolve service for type 'Microsoft.AspNetCore.SignalR.Client.HubConnection' while attempting to activate 'User.Implementation.Grains.NotifierGrain'.
Orleans supports constructor injection, so you can inject the SignalRClient into your grain constructor. In your code you are already correctly registering the client using services.AddSingleton(SignalRClient), so I will focus on how to inject the type into your grain.
I do not know what the type the SignalR client object is, but in this example I assume that the type is "SignalRClient":
[StatelessWorker]
[Reentrant]
public class NotifierGrain : Grain, INotifierGrain
{
private readonly SignalRClient signalRClient;
public NotifierGrain(SignalRClient signalRClient)
{
this.signalRClient = signalRClient;
}
public async Task NotifyClients(object message, MessageType type)
{
var registerUserNotification = (RegisterUserNotificationModel)message;
await this.signalRClient.SendAsync(
MessageMethods.RegisterUserToMultipleGroups,
registerUserNotification.UserId,
registerUserNotification.infoIds);
}
}
Depends how you're thinking to use SignalR Server, if you're going to host your SignalR server with Microsoft Orleans for sure you need to have backplane to handle the Orleans cluster communications.
You can use SignalR Orleans which has everything done out of the box for you :)
Also if you need a reactive SignalR library for the frontend, you can use Sketch7 SignalR Client
PS I m one of the authors of both libraries.
We have an MVC5 SPA application where one of our Web API controllers needs to process the import of a file. This process is made once per month, and can take up to 5 hours (our client wants this import to be made via Web API).
Our problem is that the connection is lost (ERR_CONNECTION_RESET) after one hour, and we need to keep it opened during the entire process.
We think one option is to send partial content from the server, to let the browser know the connection is still active and processing. In MVC Controller, something like this:
public class PartialActionResult : ActionResult
{
private Counter _counter;
private Action<Counter> _counterIncrease;
public PartialActionResult(Action<Counter> counterIncrease)
{
_counter = new Counter();
_counterIncrease = counterIncrease;
}
public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.ContentType = "text/html";
context.HttpContext.Response.BufferOutput = true;
if (context.HttpContext.Response.IsClientConnected)
{
while (_counter.Value < 100)
{
_counterIncrease(_counter);
context.HttpContext.Response.Write("processing...");
if (_counter.Value == 100)
context.HttpContext.Response.Write("OK");
context.HttpContext.Response.Flush();
}
}
}
}
With this, we can pass a delegate as reference, and from there we can update the information of the import status.
Is there any option for Web API, as this ActionResult only works for MVC Controllers? Other than that, any option to maintain the connection opened? BTW we cannot use signalR, just in case that might work.
Thanks in advance
Below I present a part of an Azure Web App that handles a device notification logic. What I'm doing is invoking the code shown below from a ASP MVC Controller.
But when I run it I get an hung (ever-pending) request from the browser.
I thought I've found a workaround by wrapping the SendAsync call in a BackgroundWorker thread. It's better, but I doesn't work right. For first couple of times (one or two) it works ok, but then it happens again, the wrapped thread hangs.
The code is not far different from the one on MSDN for a console application. What am I missing?
using System.Web.Configuration;
using Microsoft.Azure.Devices;
namespace MyTest
{
public class Sender
{
private readonly string connectionString;
private readonly Microsoft.Azure.Devices.ServiceClient serviceClient;
public Sender()
{
connectionString = WebConfigurationManager.AppSettings["ConnectionString"];
serviceClient = ServiceClient.CreateFromConnectionString(connectionString);
}
public async void SendRequest(string deviceId, string msgText)
{
var message = new Message();
message.Properties.Add("text", msgText));
await serviceClient.SendAsync(deviceId, message);
}
}
}
The problem was caused by inappropriate usage of ASP MVC framework.
It turned out that AsyncController has to be used instead of just Controller when a long running async\await is utilized. The pipeline must be async all the way.
I am using SingalR for my Chat Application. Wanted to play with Redis
and SignalR but I cannot find an working example where i can send msg to
specific connectionId. Below Code that works for a single server instance.
But when i make it a Web Garden with 3 process it stops working as my
server instance that gets the message cannot find the connectionId
for that destination UserId to send the message.
private readonly static ConnectionMapping<string> _connections = new ConnectionMapping<string>();
public void Send(string sendTo, string message, string from)
{
string fromclientid = Context.QueryString["clientid"];
foreach (var connectionId in _connections.GetConnections(sendTo))
{
Clients.Client(connectionId).send(fromclientid, message);
}
Clients.Caller.send(sendTo, "me: " + message);
}
public override Task OnConnected()
{
int clientid = Convert.ToInt32(Context.QueryString["clientid"]);
_connections.Add(clientid.ToString(), Context.ConnectionId);
}
I have used the example below to setup my box and code but none of
them have examples for sending from one client to specific client or
group of specific clients.
http://www.asp.net/signalr/overview/performance-and-scaling/scaleout-with-redis
https://github.com/mickdelaney/SignalR.Redis/tree/master/Redis.Sample
The ConnectionMapping instance in your Hub class will not synced across different SignalR server instances. You need to use permanent external storage such as a database or a Windows Azure table. Refer to this link for more details:
http://www.asp.net/signalr/overview/hubs-api/mapping-users-to-connections
I've been trying to get the GetClientAccessToken flow to work with the latest release 4.1.0 (via nuget), where I'm in control of all three parties: client, authorization server and resource server.
The situation I have started to prototype is that of a Windows client app (my client - eventually it will be WinRT but its just a seperate MVC 4 app right now to keep it simple), and a set of resources in a WebAPI project. I'm exposing a partial authorization server as a controller in the same WebAPI project right now.
Every time (and it seems regardless of the client type e.g. UserAgentClient or WebServerClient) I try GetClientAccessToken, by the time the request makes it to the auth server there is no clientIdentifier as part of the request, and so the request fails with:
2012-10-15 13:40:16,333 [41 ] INFO {Channel} Prepared outgoing AccessTokenFailedResponse (2.0) message for <response>:
error: invalid_client
error_description: The client secret was incorrect.
I've debugged through the source into DNOA and essentially the credentials I'm establishing on the client are getting wiped out by NetworkCredential.ApplyClientCredential inside ClientBase.RequestAccessToken. If I modify clientIdentifier to something reasonable, I can track through the rest of my code and see the correct lookups/checks being made, so I'm fairly confident the auth server code is ok.
My test client currently looks like this:
public class AuthTestController : Controller
{
public static AuthorizationServerDescription AuthenticationServerDescription
{
get
{
return new AuthorizationServerDescription()
{
TokenEndpoint = new Uri("http://api.leave-now.com/OAuth/Token"),
AuthorizationEndpoint = new Uri("http://api.leave-now.com/OAuth/Authorise")
};
}
}
public async Task<ActionResult> Index()
{
var wsclient = new WebServerClient(AuthenticationServerDescription, "KieranBenton.LeaveNow.Metro", "testsecret");
var appclient = new DotNetOpenAuth.OAuth2.UserAgentClient(AuthenticationServerDescription, "KieranBenton.LeaveNow.Metro", "testsecret");
var cat = appclient.GetClientAccessToken(new[] { "https://api.leave-now.com/journeys/" });
// Acting as the Leave Now client we have access to the users credentials anyway
// TODO: CANNOT do this without SSL (turn off the bits in web.config on BOTH sides)
/*var state = client.ExchangeUserCredentialForToken("kieranbenton", "password", new[] { "https://api.leave-now.com/journeys/" });
// Attempt to talk to the APIs WITH the access token
var resourceclient = new OAuthHttpClient(state.AccessToken);
var response = await resourceclient.GetAsync("https://api.leave-now.com/journeys/");
string sresponse = await response.Content.ReadAsStringAsync();*/
// A wrong one
/*var wresourceclient = new OAuthHttpClient("blah blah");
var wresponse = await wresourceclient.GetAsync("https://api.leave-now.com/journeys/");
string wsresponse = await wresponse.Content.ReadAsStringAsync();
// And none
var nresourceclient = new HttpClient();
var nresponse = await nresourceclient.GetAsync("https://api.leave-now.com/journeys/");
string nsresponse = await nresponse.Content.ReadAsStringAsync();*/
return Content("");
}
}
I can't figure out how to prevent this or if its by design what I'm doing incorrectly.
Any help appreciated.
The NetworkCredentialApplicator clears the client_id and secret from the outgoing message as you see, but it applies it as an HTTP Authorization header. However, HttpWebRequest clears that header on the way out, and only restores its value if the server responds with an HTTP error and a WWW-Authenticate header. It's quite bizarre behavior on .NET's part, if you ask me, to suppress the credential on the first outbound request.
So if the response from the auth server is correct (at least, what the .NET client is expecting) then the request will go out twice, and work the second time. Otherwise, you might try using the PostParameterApplicator instead.