public async Task ApplyChangedSettings()
{
if (ConnectionTimeEntryHub.State == Microsoft.AspNet.SignalR.Client.ConnectionState.Connected)
{
var d = await HubProxy.Invoke<TimeEntryV2.Models.Models.UpdatingSettings>("GetSettings", User.UserIdentity);
TimeEntrySettings = d;
}
}
Above method is created in wpf client viewmodel , I want to call this method from class outside hub in my asp.net web application as per documetation
var hubContext = Microsoft.AspNet.SignalR.GlobalHost.ConnectionManager.GetHubContext<TimeEntryHub
s.TimeEntrySettingsHub>();
if (TimeEntryHubs.TimeEntrySettingsHub.Users.Any(f => f.Value == userId))
{
var connectionID = TimeEntryHubs.TimeEntrySettingsHub.Users.Where(f => f.Value == userId).Single().Key;
hubContext.Clients.Client(connectionID).ApplyChangedSettings();
}
I am getting hub context as above , but its not doing anything ,It gets executed silently without any errors and does not reflect any changes
I stepped through the code its not calling the method on .net client , breakpoint on client method is not reached
Using Dependencyresolver solved my problem
on Server Side
var dependencyResolver = GlobalHost.DependencyResolver;
var connectionManager = dependencyResolver.Resolve<IConnectionManager>();
var hubContext = connectionManager.GetHubContext<TimeEntryHubs.TimeEntrySettingsHub>();
if (TimeEntryHubs.TimeEntrySettingsHub.Users.Any(f => f.Key == userId))
{
var connectionID = TimeEntryHubs.TimeEntrySettingsHub.Users.Where(f => f.Key == userId).Single().Value;
hubContext.Clients.Client(connectionID).ApplyChangedSettings();
}
On client Side
HubProxy.On("ApplyChangedSettings", () => ApplyChangedSettings().ConfigureAwait(false));
public async Task ApplyChangedSettings()
{
if (ConnectionTimeEntryHub.State == Microsoft.AspNet.SignalR.Client.ConnectionState.Connected)
{
await ApplySettings().ConfigureAwait(false);
}
}
Related
I can't find a solution to the problem despite many similar questions.
There is a Web API. On POST I need
read DB
make a HTTP call to other service to subscribe on notification (let's say it takes 5s)
return the data from the DB
In the step 2, I don't need to wait, I don't need to block the client (for 5sec), so the client should not wait for the response.
However, the server have to wait on result from 2 and log it. So far I've tried
[HttpPost("{callId}")]
public async Task<IActionResult> CreateSubs([FromRoute] string callId)
{
var data = await ...// read the DB
_ = SubscribeForUpdates(callId);
return Ok(data);
}
private async Task SubscribeForUpdates(string callId)
{
_logger.LogInformation("Subscribe client {ConnectionId} notifications", callId);
var requestMessage = new HttpRequestMessage
{
RequestUri = new Uri(_httpClient.BaseAddress, $"subscribe/{callId}"),
Method = HttpMethod.Get,
};
var result = await SendAsync<SubscriptionResponse>(requestMessage);
if (result.IsSuccess)
{
Console.WriteLine("Success");
}
else
{
Console.WriteLine("Fail");
}
}
SendAsync is from some library and so smth like _httpClient.SendAsync
In this case the request will not be blocked, the internal HTTP request is successful but I there is no Success from Console.WriteLine("Success");. Only if I put a breakpoint there it logs.
Could you please help me to understand why this is not log and how to fix that?
I've tried ContinueWith - no result
await SendAsync<ServerSubscriptionResponse>(requestMessage)
.ContinueWith(t =>
{
if (t.Result.IsSuccess)
{
Console.WriteLine("Success");
}
else
{
Console.WriteLine("Fail");
}
})
When I use await SubscribeForUpdates(callId) inasted of _ = SubscribeForUpdates(callId) it works and logs but the blocks a client. I need to avoid that
I newbe to Signal R Core here. i am try to connect to a Signal R client to A Serverless hub. In my example for now i just want to create multiple instances of en get a connection every time a new one is connected.
static async Task Main(string[] args)
{
var client = new HttpClient();
var response = await client.GetAsync("http://localhost:7071/api/negotiate?userid=1");
response.EnsureSuccessStatusCode();
var responseBody = await response.Content.ReadAsStringAsync();
var d = JsonConvert.DeserializeObject<dynamic>(responseBody);
string urlString = Convert.ToString(d.Url);
var connection = new HubConnectionBuilder()
.ConfigureLogging(logging =>
{
// Set the log level of signalr stuffs
logging.AddFilter("Microsoft.AspNetCore.SignalR", LogLevel.Debug);
})
.WithUrl(urlString, options => options.AccessTokenProvider = () => Task.FromResult(Convert.ToString(d.AccessToken)))
.Build();
connection.On<NewConnection>("newConnection", c => OnReceiveMessage(c));
await connection.StartAsync();
// while (connection.State == HubConnectionState.Connected)
// {
// }
Console.Read();
}
private static void OnReceiveMessage(NewConnection connection)
{
Console.WriteLine($"user {connection.UserId} with connctionid {connection.ConnectionId} has been connected");
}
On my Javascript client this is working but on my Net client it keeps disconnecting everytime right after connection:
[2022-10-02T11:03:27.542Z] Executing 'Functions.OnConnected' (Reason='(null)', Id=3878244a-50cd-47a6-b8c6-98ed1c0dd5c6)
[2022-10-02T11:03:27.544Z] KimOo3chMz6M9zxEPAlpzQ has connected
[2022-10-02T11:03:27.549Z] Executed 'Functions.OnConnected' (Succeeded, Id=3878244a-50cd-47a6-b8c6-98ed1c0dd5c6, Duration=7ms)
[2022-10-02T11:03:27.571Z] Executing 'Functions.OnDisconnected' (Reason='(null)', Id=693bec34-3ed3-434c-93fb-e366c1113e4a)
[2022-10-02T11:03:27.572Z] KimOo3chMz6M9zxEPAlpzQ has disconnected
Not sure what i am missing at the moment.
Found the answer. This behavior was cause by the fact that websockets are not supported by default in Net-Core console app. by changing this to longpolling this as solved.
My problem is:
I have a WPF app and a Xamarin Forms app. I want to create a chat. And it works when I call from WPF to a method that should send a message to every user. But it doesn't when I choose a specific user. In that case the "On" method on the Xamarin site is not fired. Probably I messed up something with logging a user. Here is my code.
Xamarin
var hubConnection = new HubConnection("my_url");
hubConnection.Headers.Add("login", CacheUtils.User.login);
ChatHubProxy = hubConnection.CreateHubProxy("ChatHub");
await hubConnection.Start();
ChatHubProxy.On<string, string>("UpdateChatMessage", (message, username) =>
{
Device.BeginInvokeOnMainThread(() =>
{
MessagingCenter.Send(this, "AddMessage", new Message() { Text = message, User = username });
});
});
string lConnectionId = await ChatHubProxy.Invoke<string>("Login");
hubConnection.ConnectionId = lConnectionId;
So I basically create a connection and subscribe to an "On" method. In the header I pass a user login. As I said before receiving messages works when the message is for everyone.
On the server side I first call a method "Login" which adds user to my ConcurrentDictionary. Then in a method "SendMessageToSpecificUser" I get user from this dictionary I call "UpdateChatMessage".
Server side
private static ConcurrentDictionary<string, string> clients = new ConcurrentDictionary<string, string>();
public void SendMessage(string message, string username)
{
Clients.All.UpdateChatMessage(message, username); //this works
}
public void SendMessageToSpecificUser(string message, string login)
{
string lUserId = clients.Where(x => x.Value == login).FirstOrDefault().Key;
if (!string.IsNullOrEmpty(lUserId))
Clients.User(lUserId).UpdateChatMessage(message, login); //this doesn't work
}
public string Login()
{
string username = Context.Headers.Get("login");
if (clients.ContainsKey(username))
((IDictionary)clients).Remove(username);
clients.TryAdd(Context.ConnectionId, username);
return Context.ConnectionId;
}
Any help appreciated :)
Ok, I found the solution. I should use:
Clients.Client(lUserId).UpdateChatMessage(message, login);
instead of
Clients.User(lUserId).UpdateChatMessage(message, login);
I have 2 Vue.js clients that share the same backend server (ASP.NET Core). One of the clients is an e-commerce site, and the other client is the admin dashboard that manages the orders.
I have implemented SignalR to allow live order updates on the order dashboard table. The updates currently work in real time without the need to refresh the browser when I update an order's status on the admin dashboard, e.g. when updating from "Out for Delivery" to "Delivered" on browser 1, browser 2 displays the changes immediately without having to refresh the page.
Order updated via dashboard, SignalR event received
However, when I create a new order from the e-commerce site, even though I have checked that my code enters the Hub method to broadcast the event and the new order details to all clients, the order dashboard does not update/refresh, and it does not receive any events as well when I checked the dev tools. I would appreciate some help on this problem.
Hub methods in mounted()
// Establish hub connection
this.connection = await OrderHub.connectToOrderHub();
// Establish hub methods
this.connection.on("OneOrder", order => {
console.log("OneOrder called");
console.log(order);
this.getAllOrders();
});
this.connection.on("MultipleOrders", orders => {
console.log("MultipleOrders called");
console.log(orders);
this.getAllOrders();
});
// start the connection
this.connection
.start()
.then(() => {
console.log("Connection to hub started");
})
.catch(err => console.log(err));
orderHub.js
const signalR = require("#aspnet/signalr");
class OrderHub {
async connectToOrderHub() {
return new signalR.HubConnectionBuilder()
.withUrl("https://localhost:44393/order-hub")
.configureLogging(signalR.LogLevel.Error)
.build();
}
}
export default new OrderHub();
OrderHub.cs in server side
public interface IOrderHub
{
Task NotifyOneChange(Order newOrder);
Task NotifyMultipleChanges(List<Order> newOrders);
}
public class OrderHub : Hub, IOrderHub
{
private readonly IHubContext<OrderHub> _hubContext;
public OrderHub(IHubContext<OrderHub> hubContext)
{
_hubContext = hubContext;
}
public async Task NotifyOneChange(Order newOrder)
{
await _hubContext.Clients.All.SendAsync("OneOrder", newOrder);
}
public async Task NotifyMultipleChanges(List<Order> newOrders)
{
await _hubContext.Clients.All.SendAsync("MultipleOrders", newOrders);
}
}
Create Order method
public async Task<Order> Create(Order order)
{
Order newOrder;
try
{
List<string> imgKeys = new List<string>();
foreach (OrderItem item in order.OrderItems)
{
imgKeys.Add(item.OrderImageKey);
}
// make images on s3 permanent
List<string> imgUrls = await _s3Service.CopyImagesAsync(imgKeys);
// put the new images url into object
for (int i = 0; i < order.OrderItems.Count; i++)
{
order.OrderItems.ElementAt(i).OrderImageUrl = imgUrls.ElementAt(i);
}
// create new order object to be added
newOrder = new Order()
{
CreatedAt = DateTime.Now,
UpdatedAt = DateTime.Now,
OrderSubtotal = decimal.Parse(order.OrderSubtotal.ToString()),
OrderTotal = decimal.Parse(order.OrderTotal.ToString()),
ReferenceNo = order.ReferenceNo,
Request = order.Request,
Email = EncryptString(order.EmailString, encryptionKey),
UpdatedById = order.UpdatedById,
DeliveryTypeId = order.DeliveryTypeId,
Address = order.Address,
StatusId = 1,
OrderItems = order.OrderItems
};
// add to database
await _context.Orders.AddAsync(newOrder);
await _context.SaveChangesAsync();
}
catch (Exception ex)
{
throw new AppException("Unable to create product record.", new { message = ex.Message });
}
await _orderHub.NotifyOneChange(newOrder); // it steps inside & executes the method, but client side does not receive any events
// returns product once done
return newOrder;
}
I managed to figure out the cause of this issue. SignalR seems to break when the message is over a certain size, and this issue is referenced here. To fix this, I decided to pass in the order's ID instead of the whole order object, and do another GET call to retrieve the details in a separate API call to keep the SignalR message small.
Given a certain number of request objects (max 9), i need to call a web service endpoint the same number of times asynchronously. With .NET 4.0, we used delegate and IAsyncResult to achieve this.
Is there a better way to do this with asyc/await, TPL or both of them combined with .NET 4.6.1?
Will using Parallel.ForEach with ConcurrentBag be optimal as suggested in this answer?
Synchronous Code Example:
public List<WbsResponse> GetWbsResults()
{
List<WbsRequest> requests = CompileWbsRequests();
List<WbsResponse> results = new List<WbsResponse>();
foreach (var request in requests)
{
//Call same web service endpoint n number of times
var response = CallWebService(request);
results.Add(response);
}
//do something with results
return results;
}
private WbsResponse CallWebService(WbsRequest request)
{
//Call web service
}
Edit/Update 1: Based on #Thierry's answer, i've created a sample code assuming there's an Order property in both the request and response objects to mark the request/response ordering:
public List<WbsResponse> GetWbsResults()
{
List<WbsRequest> requests = CompileWbsRequests();
List<WbsResponse> results = new List<WbsResponse>();
Parallel.ForEach(requests, (request) => {
var response = CallWebService(request);
response.Order = request.Order;
results.Add(response);
});
results = results.OrderBy(r => r.Order).ToList();
//do something with results
return results;
}
private WbsResponse CallWebService(WbsRequest request)
{
//Call web service
}
Edit/Update 2: Based on this thread, i've made a few changes to Update 1:
await Task.Run(() => {
Parallel.ForEach(requests, (request) => {
var response = CallWebService(request);
response.Order = request.Order;
results.Add(response);
});
});
Requirement Summary:
Make multiple web service requests asynchronously to the same endpoint with different parameters.
Add web service results to a list in the same order as the request was made (as if it was synchronous).
Because each task finish with diffrent moment, I think you should numero the request and ordered the responses by this numero.
In the request, you init a numero and pass this numero for the response associated. Finally, when I have the results, I order it.
Like this:
public async Task<List<WbsResponse>> GetWbsResults()
{
List<WbsRequest> requests = CompileWbsRequests();
List<Task<WbsResponse>> tasks = new List<Task<WbsResponse>>();
for (var i = 0; i < requests.Count; i++)
{
var task = new Task<WbsResponse>(() => { CallWebService(WbsRequest); });
tasks.Add(task);
}
var responses = await Task.WhenAll(tasks);
var responsesOrdered = responses.OrderBy(r => r.Order)
//do something with results
return results;
}
public List<WbsRequest> CompileWbsRequests()
{
//create requests
foreach(var request in requests)
{
request.Order += 1;
}
}
private WbsResponse CallWebService(WbsRequest request)
{
//Call web service
reponse.order = request.order;
return reponse;
}
I think you can use Task.WaitAll to make the code work in async way and it will look prettier as well:
public List<WbsResponse> GetWbsResults()
{
List<WbsRequest> requests = CompileWbsRequests();
var responses = await Task.WhenAll(requests.Select(CallWebService));
return responses;
}
but you have to modify this method as below to return a task:
private async Task<WbsResponse> CallWebService(WbsRequest request)
{
//Call web service
}