I have followed the documentation on how to use signalr in blazor server ,in the hub i have setup a method with this signature
public async Task SendMessage(string sender, string receiver, string message)
{
await Clients.Users(sender, receiver).SendAsync("ReceiveMessage", receiver, message);
}
And in the client side(the page on blazor server) i have set this up for the ReceiveMessage method
public async Task OnInitializeAsync()
{
if(hub is null)
hub = new HubConnectionBuilder()
.WithUrl(_NavigationManager.ToAbsoluteUri("/ConnectionsHub"))
.Build();
hub.On<string, string>($"ReceiveMessage", // this is never triggered
async (sender, message) =>
{
var encodedMsg = $"{sender}: {message}";
ChatBox.MessageList.Add(encodedMsg);
await ChatBox.ComponentStateHasChanged();
});
hub.ServerTimeout = TimeSpan.FromMilliseconds(100000);
await hub.StartAsync();
}
and how I send the message
private async Task SendMessage(string message)
{
if(string.IsNullOrEmpty(message))
{
await PrintMessage("Error", "Cannot send an empty message");
return;
}
if (hub is not null)
{
foreach (var user in _UserList)
{
await hub.SendAsync("SendMessage", ThisUser.Email, user.User.Email,ChatBox.Message);
}
}
}
Related
I am a new user to bUnit and have managed to get some tests running for NavMenu to grasp the basic concepts.
However, a different Blazor page makes a request to a secondary signalR hub for communication of workflow state.
How to mock signalR connection?
https://github.com/dotnet/aspnetcore/issues/14924
Server Page that uses an additional signalR connection to communicate workflow state
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var hubUrl = NavigationManager.BaseUri.TrimEnd('/') + "/motionhub";
try
{
Logger.LogInformation("Index.razor page is performing initial render, connecting to secondary signalR hub");
hubConnection = new HubConnectionBuilder()
.WithUrl(hubUrl)
.ConfigureLogging(logging =>
{
logging.AddConsole();
logging.AddFilter("Microsoft.AspNetCore.SignalR", LogLevel.Information);
})
.AddJsonProtocol(options =>
{
options.PayloadSerializerOptions = JsonConvertersFactory.CreateDefaultJsonConverters(LoggerMotionDetection, LoggerMotionInfo, LoggerJsonVisitor);
})
.Build();
hubConnection.On<MotionDetection>("ReceiveMotionDetection", ReceiveMessage);
hubConnection.Closed += CloseHandler;
Logger.LogInformation("Starting HubConnection");
await hubConnection.StartAsync();
Logger.LogInformation("Index Razor Page initialised, listening on signalR hub => " + hubUrl.ToString());
}
catch (Exception e)
{
Logger.LogError(e, "Encountered exception => " + e);
}
}
}
Stub Unit Test Class
public class IndexTest : TestContext, IDisposable
{
private MotionDetectionRepository repo;
public IndexTest()
{
var mock = new Mock<IMotionDetectionSerializer<MotionDetection>>();
repo = new MotionDetectionRepository(mock.Object, new NullLogger<MotionDetectionRepository>());
Services.AddScoped<ILogger<MotionDetectionRepository>, NullLogger<MotionDetectionRepository>>();
Services.AddScoped<ILogger<MotionInfoConverter>, NullLogger<MotionInfoConverter>>();
Services.AddScoped<ILogger<MotionDetectionConverter>, NullLogger<MotionDetectionConverter>>();
Services.AddScoped<ILogger<JsonVisitor>, NullLogger<JsonVisitor>>();
Services.AddScoped<ILogger<WebApp.Pages.Index>, NullLogger<WebApp.Pages.Index>>();
Services.AddScoped<IMotionDetectionRepository>(sp => repo);
Services.AddScoped<MockNavigationManager>();
Services.AddSignalR();
}
[Fact]
public void Test()
{
var cut = RenderComponent<WebApp.Pages.Index>();
Console.WriteLine($"{cut.Markup}");
}
}
I use these brokers.
public interface IHubConnectionsBroker
{
HubConnection CreateHubConnection(string endpoint, bool autoReconnect);
}
public interface IChatHubBroker
{
event EventHandler<ChatMessage> OnReceiveMessage;
event EventHandler<Exception> OnConnectionClosed;
event EventHandler<Exception> OnReconnecting;
event EventHandler<string> OnReconnected;
bool IsConnected { get; }
string ConnectionId { get; }
TimeSpan HandshakeTimeout { get; set; }
TimeSpan KeepAliveInterval { get; set; }
TimeSpan ServerTimeout { get; set; }
ChatHubBrokerState ChatHubBrokerState { get; }
ValueTask StartAsync();
ValueTask StopAsync();
ValueTask SendAsync(ChatMessage message);
ValueTask DisposeAsync();
}
My .net core3.1 web application has say 4 microservices (MasterMS, PartyMS, ProductMS, PurchaseMS)
and uses Rabbitmq as message broker.
In one specific scenario, the MasterMS publishes an event (insert/update in Company table) to Rabbitmq exchange (xAlexa), from where it is fanned-out to the respective queues of all subscribing MSs (PartyMS, ProductMS).
PartyMS get the event from CompanyEventPartyMS queue and ProductMS gets it from CompanyEventProductMS queue. Thereby both Party and Product updates their respective Company table and everything is in Sync and perfect. Btw, PurchaseMS is not subscribing and so not bothered.
Now comes the real problem.. The subscribing MSs (Consumers) does not respond when their web page is requested. PartyMS and ProductMS webpages throws SocketException, while the non-subscriber PurchaseMS works fine. Now if i Comment out the line where PartyMS subscribes, it starts working again though it no longer gets the CompanyEvent and goes out-of-sync.
Any insights friends ?
SocketException: No connection could be made because the target machine actively refused it.
System.Net.Http.ConnectHelper.ConnectAsync(string host, int port, CancellationToken cancellationToken)
public void Publish<T>(T #event) where T : Event
{
var factory = new ConnectionFactory() { HostName = "localhost" };
using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
channel.ExchangeDeclare(exchange: "xAlexa", type: ExchangeType.Fanout);
var message = JsonConvert.SerializeObject(#event);
var body = Encoding.UTF8.GetBytes(message);
var eventName = #event.GetType().Name;
channel.BasicPublish(exchange: "xAlexa",
routingKey: eventName, //string.Empty,
basicProperties: null,
body: body);
}
}
StartBasicConsume
private void StartBasicConsume<T>() where T : Event
{
var factory = new ConnectionFactory()
{
HostName = "localhost",
DispatchConsumersAsync = true
};
var connection = factory.CreateConnection();
var channel = connection.CreateModel();
var eventName = typeof(T).Name;
var msName = typeof(T).FullName;
string[] str = { };
str = msName.Split('.');
eventName += str[1];
channel.ExchangeDeclare(exchange: "xAlexa",
type: ExchangeType.Fanout);
channel.QueueDeclare(eventName, true, false, false, null); //channel.QueueDeclare().QueueName;
channel.QueueBind(queue: eventName,
exchange: "xAlexa",
routingKey: string.Empty);
var consumer = new AsyncEventingBasicConsumer(channel);
consumer.Received += Consumer_Received;
channel.BasicConsume(eventName, true, consumer);
Console.WriteLine("Consumer Started");
Console.ReadLine();
}
private async Task Consumer_Received(object sender, BasicDeliverEventArgs e)
{
var eventName = e.RoutingKey;
var body = e.Body.ToArray();
//var body = e.Body.Span;
var message = Encoding.UTF8.GetString(body);
//var message = Encoding.UTF8.GetString(e.Body);
Console.WriteLine(message);
try
{
await ProcessEvent(eventName, message).ConfigureAwait(false);
}
catch (Exception ex)
{
}
}
The call to ProductsMS Api from MVC app (here is where it fails if subscribed and works if not subscribed to CompanyEvent !)
public class ProductService:IProductService
{
private readonly HttpClient _apiCLient;
public ProductService(HttpClient apiCLient)
{
_apiCLient = apiCLient;
}
public async Task<List<Product>> GetProducts()
{
var uri = "https://localhost:5005/api/ProductApi";
List<Product> userList = new List<Product>();
HttpResponseMessage response = await _apiCLient.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
var readTask = response.Content.ReadAsStringAsync().Result;
userList = JsonConvert.DeserializeObject<List<Product>>(readTask);
}
return userList;
}
}
Find ProductsMS Api Startup.cs below:
namespace Alexa.ProductMS.Api
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
var connectionString = Configuration["DbContextSettings:ConnectionString"];
var dbPassword = Configuration["DbContextSettings:DbPassword"];
var builder = new NpgsqlConnectionStringBuilder(connectionString)
{
Password = dbPassword
};
services.AddDbContext<ProductsDBContext>(opts => opts.UseNpgsql(builder.ConnectionString));
services.AddMediatR(typeof(Startup));
RegisterServices(services);
}
private void RegisterServices(IServiceCollection services)
{
DependencyContainer.RegisterServices(services);
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
});
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
ConfigureEventBus(app); //WORKS IF COMMENTED; FAILS OTHERWISE <---
}
private void ConfigureEventBus(IApplicationBuilder app)
{
var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();
eventBus.Subscribe<CompanyEvent, CompanyEventHandler>();
eventBus.Subscribe<PartyEvent, PartyEventHandler>();
}
}
}
Also see the images:
RabbitMQ fanout exchange
RabbitMQ Queues
exchange
queues
Remove the last line Console.ReadLine(); of the method StartBasicConsume().
When we are using that line in the function it is waiting for any key press or any input.
I have two subscribers to the same event. One subscriber writes to the database and the other caches data in memory. The former takes much longer than the latter and that's OK since the caching is more time critical than writing to the database. Some times the DB writer gets behind and its queue starts to grow, and that's OK (as long as eventually it catches up). But, it's unacceptable for the caching subscriber to get behind. It's OK for it to can get ahead of the DB writer when the DB writer cannot keep up. I want the two queues to be processed as fast as possible without the processing of one affecting the processing of the other.
But, what I see is that when the DB writer queue grows, the caching subscriber's queue grows. The two queues have the same number of pending items. They seem to be in lock step.
Profiling shows that the DB write takes about 500 times longer than the memory cache (which is not surprising). So, the caching subscriber could easily keep up, but it seems to be held back by the DB writer subscriber.
I'll apologize for code up front. It would be easier to understand if not for the endpoint wrapper code.
This method creates the endpoint as en event publisher. (My wrapper code provides different endpoint types for different purposes. I find the NSB endpoint abstraction to be lacking):
public async Task Start()
{
if (_endpoint == null)
_endpoint = await EventPublisherEndpoint.Start(EndpointName);
}
Here's the EventPublisherEndpoint:
public sealed class EventPublisherEndpoint : Endpoint, IEventPublisherEndpoint
{
private EventPublisherEndpoint(IEndpointInstance nsbEndpoint, string name) : base(nsbEndpoint, name) { }
/// <summary>
/// Create and start endpoint.
/// </summary>
public static async Task<IEventPublisherEndpoint> Start(string endpointName)
{
var ep = await ConfigureEndpoint(new EndpointConfig(), endpointName);
return new EventPublisherEndpoint(ep, endpointName);
}
public async Task Publish(object message)
{
await NsbEndpoint.Publish(message);
}
}
Here's the general endpoint factory:
protected static async Task<IEndpointInstance> ConfigureEndpoint(
IEndpointConfig config,
string endpointName,
Action<EndpointConfiguration> configureEndpoint = null,
Action<TransportExtensions<MsmqTransport>> configureTransport = null,
Action<PersistenceExtensions<SqlPersistence>> configurePersistence = null)
{
if (config == null)
throw new ArgumentNullException(nameof(config));
if (endpointName == null)
throw new ArgumentNullException(nameof(endpointName));
var fullName = GetFullName(endpointName);
try
{
IEndpointInstance ep = null;
await Logger.OperationAsync("Start endpoint: " + endpointName, async () =>
{
// eliminate existing error queue to clear it; is re-created below
string errorEndpointName = $"{fullName}.error";
EliminateQueue(errorEndpointName);
var endpointConfiguration = new EndpointConfiguration(fullName);
endpointConfiguration.UseSerialization<JsonSerializer>();
endpointConfiguration.SendFailedMessagesTo(errorEndpointName);
endpointConfiguration.EnableInstallers();
endpointConfiguration.DoNotCreateQueues(); // created explicitly (below)
endpointConfiguration.DefineCriticalErrorAction(OnCriticalError);
endpointConfiguration.LimitMessageProcessingConcurrencyTo(config.MaxConcurrency);
configureEndpoint?.Invoke(endpointConfiguration);
// NOTE:
// Using TransportTransactionMode.None disables retries.
// Using default (whatever that value is) results in runtime error on endpoint
// config that distributed transactions are not enabled (whatever that means).
// Stumbled into SendsAtomicWithReceive which seems to work; no error and
// retries happen.
const TransportTransactionMode transactionMode = TransportTransactionMode.SendsAtomicWithReceive;
var transport = endpointConfiguration
.UseTransport<MsmqTransport>()
.Transactions(transactionMode);
{
var routing = transport.Routing();
var instanceMappingFile = routing.InstanceMappingFile();
instanceMappingFile.FilePath(NsbInstanceMappingFile.DefaultFilePath);
}
configureTransport?.Invoke(transport);
var persistence = endpointConfiguration.UsePersistence<SqlPersistence>();
persistence.SqlVariant(SqlVariant.MsSqlServer);
persistence.ConnectionBuilder(() => new SqlConnection(config.PersistConnectionString));
persistence.TablePrefix(fullName + ".");
configurePersistence?.Invoke(persistence);
var subscriptions = persistence.SubscriptionSettings();
subscriptions.CacheFor(SubscriptionCachePeriod);
EnsureQueuesForEndpoint(fullName);
ep = await NServiceBus.Endpoint.Start(endpointConfiguration);
});
return ep;
}
catch (Exception exception)
{
throw new ApplicationException($"Unable to start endpoint '{endpointName}' for connection [{config.PersistConnectionString}].", exception);
}
}
Here's the publish code (inside a logging call):
public async Task Publish(DeviceOutput message)
{
await Logger.OperationWithoutBeginLogOrThrowAsync(
() => $"Publishing {message.GetType().Name} message: {message.ToString().Truncate()}.",
async () => { await _endpoint.Publish(message); });
}
Here's the event class:
public sealed class Datagram : DeviceOutput, IEvent
{
public Datagram(long deviceID, DatagramType payloadType, string payload) : base(deviceID)
{
Payload = payload;
PayloadType = payloadType;
}
public string Payload { get; set; }
public DatagramType PayloadType { get; set; }
public override string ToString()
{
return base.ToString() + Environment.NewLine + $"PayloadType:{PayloadType} Payload:{Payload.Truncate()}";
}
}
Here's one of the subscriber endpoint config:
public async Task Start()
{
if (_endpoint == null)
{
_endpoint = await EventSubscriberEndpoint<Datagram>.Start(
endpointName: "datagram-store-subscriber",
publisherEndpointName: DatagramPublisher.EndpointName);
}
}
And the associated handler:
public async Task Handle(Datagram datagram, IMessageHandlerContext context)
{
await _storer.Store(datagram);
}
Here's the other subscriber endpoint config:
public async Task Start()
{
if (_endpoint == null)
{
_endpoint = await EventSubscriberEndpoint<Datagram>.Start(
endpointName: "datagram-live-subscriber",
publisherEndpointName: DatagramPublisher.EndpointName);
}
}
And associated handler:
public async Task Handle(Datagram datagram, IMessageHandlerContext context)
{
//var start = DateTime.Now;
if (IsLiveDataEnabled)
{
await Logger.OperationWithoutBeginLogAsync(
() => $"Saving datagram: {datagram}",
async () => { await WriteData(datagram); });
}
//Logger.Info($"{DateTime.Now - start}");
}
Here's EventSubscriberEndpoint:
public sealed class EventSubscriberEndpoint<T> : Endpoint, IEventSubscriberEndpoint where T : IEvent
{
private EventSubscriberEndpoint(IEndpointInstance nsbEndpoint, string name) : base(nsbEndpoint, name) {}
public async Task Unsubscribe()
{
await NsbEndpoint.Unsubscribe(typeof(T), new UnsubscribeOptions());
}
/// <summary>
/// Create and start endpoint.
/// </summary>
public static async Task<IEventSubscriberEndpoint> Start(
string endpointName,
string publisherEndpointName,
Type[] excludeSubscriberTypes = null)
{
return await Start(endpointName, publisherEndpointName, typeof(T), excludeSubscriberTypes);
}
/// <summary>
/// Non-generic version of Start.
/// </summary>
private static async Task<IEventSubscriberEndpoint> Start(
string endpointName,
string publisherEndpointName,
Type messageType,
Type[] excludeSubscriberTypes = null)
{
return await Start(new EndpointConfig(), endpointName, publisherEndpointName, messageType, excludeSubscriberTypes);
}
private static async Task<IEventSubscriberEndpoint> Start(
IEndpointConfig config,
string endpointName,
string publisherEndpointName,
Type messageType,
Type[] excludeSubscriberTypes = null)
{
if (publisherEndpointName == null)
throw new ArgumentNullException(nameof(publisherEndpointName));
if (messageType == null)
throw new ArgumentNullException(nameof(messageType));
var ep = await ConfigureEndpoint(config,
endpointName,
endpointConfiguration =>
{
if (excludeSubscriberTypes != null)
endpointConfiguration.AssemblyScanner().ExcludeTypes(excludeSubscriberTypes);
}, transport =>
{
transport.Routing().RegisterPublisher(messageType, GetFullName(publisherEndpointName));
});
return new EventSubscriberEndpoint<T>(ep, endpointName);
}
}
Is there special config that allows queues to be processed independently of each other?
NServiceBus 6.2.1
Working on building signalR hub, I'm able to get data from hub to the client but I'm, not sure how do I push it every 1 second.
I'm not sure where do I set the timer in the controller where getApps method exists or in the hub?
Hub:
public class nphub : Hub
{
public readonly sbController _sbcontroller;
public nphub(sbController sbcontroller)
{
_sbcontroller = sbcontroller;
}
public async Task NotifyConnection()
{
IActionResult result = await _sbcontroller.getApps();
await Clients.All.SendAsync("TestBrodcasting", result);
}
}
In Controller:
public async Task<IActionResult> getApps()
{
// var request = new HttpRequestMessage(HttpMethod.Get, "apps");
// var response = await _client_NP.SendAsync(request);
// var json = await response.Content.ReadAsStringAsync();
return Ok($"Testing a Basic HUB at {DateTime.Now.ToLocalTime()}");
}
Client:
let connection = new signalR.HubConnectionBuilder()
.withUrl("/nphub").build();
connection.start().then(function () {
TestConnection();
}).catch(function (err) {
return console.error(err.toString());
});
function TestConnection() {
connection.invoke("NotifyConnection").catch(function (err) {
return console.error(err.toString());
});
}
connection.on("TestBrodcasting", function (time) {
document.getElementById('broadcastDiv').innerHTML = time.value;
document.getElementById('broadcastDiv').style.display = "block";
});
Just for the test purpose to see realtime changes, I'm trying to return time. I'm able to see time on the client but it's not changing.
You need to use a hosted service, as described in the docs. Add a class like:
internal class SignalRTimedHostedService : IHostedService, IDisposable
{
private readonly IHubContext<nphub> _hub;
private readonly ILogger _logger;
private Timer _timer;
public SignalRTimedHostedService(IHubContext<nphub> hub, ILogger<SignalRTimedHostedService> logger)
{
_hub = hub;
_logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is starting.");
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromSeconds(1));
return Task.CompletedTask;
}
private void DoWork(object state)
{
_logger.LogInformation("Timed Background Service is working.");
// send message using _hub
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}
Note: A hosted service lives in singleton scope. You can inject IHubContext<T> directly, though, because it too is in singleton scope.
Then in ConfigureServices:
services.AddHostedService<SignalRTimedHostedService>();
I have a signalR hub that needs to be able to receive an event from a client and then notify all other clients connected to the hub.
Is that possible?
I want my 'hub' application to be able to receive messages and send them. I can only figure out how to do the sending of messages. Here is what I have now:
Application 1-- Hub
Startup class:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddSignalR().AddHubOptions<EventsHub>(options =>
{
options.HandshakeTimeout = TimeSpan.FromMinutes(5);
options.EnableDetailedErrors = true;
});
services.AddTransient(typeof(BusinessLogic.EventsBusinessLogic));
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
app.UseSignalR((configure) =>
{
configure.MapHub<EventsHub>("/hubs/events", (options) =>
{
});
});
}
Set Up of the Hub in Application 1
public class EventsHub : Hub
{
public EventsHub()
{
}
public override Task OnConnectedAsync()
{
if (UserHandler.ConnectedIds.Count == 0)
{
//Do something on connect
}
UserHandler.ConnectedIds.Add(Context.ConnectionId);
Console.WriteLine("Connection:");
return base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
//Do something on Disconnect
}
public static class UserHandler
{
public static HashSet<string> ConnectedIds = new HashSet<string>();
}
}
BusinessLogic:
public class EventsBusinessLogic
{
private readonly IHubContext<EventsHub> _eventsHub;
public EventsBusinessLogic(IHubContext<EventsHub> eventsHub)
{
_eventsHub = eventsHub;
}
public async Task<Task> EventReceivedNotification(ProjectMoonEventLog eventInformation)
{
try
{
await _eventsHub.Clients.All.SendAsync("NewEvent", SomeObject);
}
catch (Exception e)
{
throw new Exception(e.Message);
}
}
}
In the second application, that listens for events or messages from the hub:
Startup.cs
private static void ConfigureAppServices(IServiceCollection services, string Orale, string Sql)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddOptions();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
//set up of singletons and transients
services.AddHostedService<Events.EventingHubClient>();
}
The ClientHub to connect to application 1:
public class EventingHubClient : IHostedService
{
private HubConnection _connection;
public EventingHubClient()
{
_connection = new HubConnectionBuilder()
.WithUrl("http://localhost:61520/hubs/events")
.Build();
_connection.On<Event>("NewEvent",
data => _ = EventReceivedNotification(data));
}
public async Task<Task> EventReceivedNotification(Event eventInformation)
{
try
{
//Do something when the event happens
return Task.CompletedTask;
}
catch (Exception e)
{
throw new Exception(e.Message);
}
}
public async Task StartAsync(CancellationToken cancellationToken)
{
// Loop is here to wait until the server is running
while (true)
{
try
{
await _connection.StartAsync(cancellationToken);
Console.WriteLine("Connected");
break;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
await Task.Delay(100);
}
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
return _connection.DisposeAsync();
}
}
This works, but now I want application 2 to be able to send a message to application 1? So I need a similar piece of code as in the EventsBusinessLogic class in application2 to send messages to application 1.
I hope this is clear enough? Is this the purpose of SignalR?
Please refer to signalR documentation signalR documentation for .net client
I guess in your Hub method like this
public async Task SendTransaction(Transaction data)
{
await Clients.All.SendAsync("TransactionReceived", data);
}
Then add methods in client side
in constructor add
connection.On<Transaction>("TransactionReceived", (data) =>
{
this.Dispatcher.Invoke(() =>
{
var transactionData = data;
});
});
and then SendTransaction expected on server
private async void SendTransaction(Transaction data)
{
try
{
await connection.InvokeAsync("SendTransaction", data);
}
catch (Exception ex)
{
//
throw
}
}