Blazor SignalR Call Wrong JSRuntime - signalr

I create Event Maped Seat reservation and using SignalR creates a realtime seat update status view
My BroadcastHub
public class BroadcastHub : Hub
{
public async Task AddToGroup(string groupName)
{
await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
}
public async Task RemoveFromGroup(string groupName)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
}
public async Task SeatUpdate(long SectinId, string groupName, long SeatId, SeatStatus seatStatus)
{
await Clients.OthersInGroup(groupName).SendAsync("ReceiveSeatUpdate", SectinId, SeatId, seatStatus);
}
}
Component
<div class="col-md-12 mb-3">
#((MarkupString)#SectionData.Salon.SalonMap)
</div>
...Seat Selection Murkup....
#code {
private HubConnection hubConnection;
public bool IsConnected => hubConnection.State == HubConnectionState.Connected;
Task SeatUpdate(long SectinId, string EventId, long SeatId, SeatStatus seatStatus) => hubConnection.SendAsync("SeatUpdate", SectinId, EventId, SeatId, seatStatus);
protected override async Task OnInitializedAsync()
{
SectionData.OnChange += StateHasChanged;
SectionData.Salon = await DataService.GetSalon();
action = GetSection;
foreach (var item in salon.Sections)
{
SectionData.Salon.SalonMap =salon.SalonMap.Replace(item.Action,$"onclick='app.GetSectionCallerJS({item.Id})'");
}
#region Hub
hubConnection = new HubConnectionBuilder().WithUrl(NavigationManager.ToAbsoluteUri("/broadcastHub")).Build();
hubConnection.On("ReceiveSeatUpdate", async (long SectinId, long SeatId, SeatStatus seatStatus) =>
{
if (SectionData.Section.Id == SectinId)
{
var Seat = SectionData.Section.Seats.Values.Where(x => x.Id == SeatId).FirstOrDefault();
Seat.SeatStatus = seatStatus;
}
StateHasChanged();
});
await hubConnection.StartAsync();
await hubConnection.SendAsync("AddToGroup", EventSansUniqueId);
#endregion Hub
}
#region GetSection
private static Action<long> action;
private void GetSection(long SectionId)
{
var section= salon.Sections.Where(x => x.Id == SectionId).FirstOrDefault();
SectionData.SetSection(section);
SectionData.Section.Seats = DataService.GetSection(SectionId);
StateHasChanged();
}
[JSInvokable]
public static void GetSectionCaller(long SectionId)
{
action.Invoke(SectionId);
}
#endregion GetSection
public void Dispose()
{
SectionData.OnChange -= StateHasChanged;
if (IsConnected) hubConnection.SendAsync("RemoveFromGroup", EventSansUniqueId);
}
}
JavaScript Is
window.app = {
GetSectionCallerJS: (id) => {
DotNet.invokeMethodAsync('KishApp.TRMS.Salon', 'GetSectionCaller', id);
}
};
The problem is when the hub registers for the second, third, and... time DotNet.invokeMethodAsync Call Last registered page, not the one actually calling the method and causing the wrong page update

tanks to #MisterMango I found that problem GetSectionCaller is a static method and I must have created new DotNetObjectReference every time page initial so
DotNetObjectReference<Salon> ObjectReference;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if (firstRender)
{
ObjectReference = DotNetObjectReference.Create<Salon>(this);
await JSRuntime.InvokeVoidAsync("app.setObjectReference", ObjectReference);
}
}
[JSInvokable("GetSectionCaller")]
public void GetSectionCaller(long SectionId)
{
GetSection(SectionId);
}
JavaScript
window.app = {
GetSectionCallerJS: (id) => {
dotNetObject.invokeMethodAsync('GetSectionCaller', id);
},
setObjectReference: (ObjectReference) => {
this.dotNetObject = ObjectReference;
}};

Related

DbContext in Service triggered by Hangfire

I have a .NET 6 Razor Pages app that triggers background tasks and then informs the user of their status via SignalR.
I'm trying to use Database1 context in the PerformBackgroundJob method, but it's disposed. What technique should I use to inject Database1 context in PerformBackgroundJob, or how else can I get this to work?
namespace Toolkat.Pages
{
public class ProcessModel : PageModel
{
private readonly Database1Context _context;
private readonly ToolkatContext _tkcontext;
private IConfiguration configuration;
private readonly IQueue _queue;
private readonly IHubContext<JobHub> _hubContext;
static ServerConnection conn;
static Server server;
static Job job;
public ProcessModel(
Database1Context context,
ToolkatContext tkcontext,
IConfiguration _configuration,
IQueue queue,
IHubContext<JobHub> hubContext)
{
_context = context;
_tkcontext = tkcontext;
configuration = _configuration;
_queue = queue;
_hubContext = hubContext;
}
public IList<CustomFileImport> CustomFileImport { get; set; } = default!;
[BindProperty]
public CustomFileImport CustomFileImportNumberTwo { get; set; } = default!;
public async Task OnGetAsync()
{
if (_context.CustomFileImports != null)
{
CustomFileImport = await _context.CustomFileImports
.Include(c => c.FileImportType)
.Include(c => c.FileImportStatus)
.Where(i => i.FileImportStatusId.Equals(1))
.ToListAsync();
}
}
public async Task<IActionResult> OnPostAsync(int[] fileImportId)
{
//Generate GUID
Guid jobId = Guid.NewGuid();
//Update FileImportItems with GUID
foreach (var id in fileImportId)
{
if (/*id == null ||*/ _context.CustomFileImports == null)
{
return NotFound();
}
var customfileimport = await _context.CustomFileImports.FirstOrDefaultAsync(m => m.FileImportId == id);
if (customfileimport == null)
{
return NotFound();
}
customfileimport.ProcessId = jobId;
await _context.SaveChangesAsync();
}
_queue.QueueAsyncTask(() => PerformBackgroundJob(jobId));
return RedirectToPage("./Result", new { jobId });
}
private async Task PerformBackgroundJob(Guid jobId /*CancellationToken cancellationToken*/)
{
await _hubContext.Clients.Group(jobId.ToString()).SendAsync("progress", "PerformBackgroundJob Started");
/*
var customFileImports = await _context.CustomFileImports
.Include(c => c.FileImportType)
.Where(i => i.ProcessId.Equals(jobId))
.ToListAsync();
*/
Debug.WriteLine("ProviderName:" + _context.Database.ProviderName);
/*
foreach (var f in customFileImports)
{
await _hubContext.Clients.Group(jobId.ToString()).SendAsync("progress", WebUtility.HtmlEncode(f.FileName));
}
*/
}
}
}
I had to combine lessons from lots of articles to figure this out. Hangfire has a nice way of approaching this.
Replace
_queue.QueueAsyncTask(() => PerformBackgroundJob(jobId));
With
BackgroundJob.Enqueue<ProcessFilesService>(x => x.DoWork());
Passing dependencies
and create this class
public class ProcessFilesService
{
IServiceProvider _serviceProvider;
public ProcessFilesService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void DoWork()
{
using var scope = _serviceProvider.CreateScope();
var ctx = scope.ServiceProvider.GetRequiredService<MyDatabaseContext>();
using var hubScope = _serviceProvider.CreateScope();
var _hubContext = hubScope.ServiceProvider.GetRequiredService<JobHub>();
Debug.WriteLine(ctx.Database.ProviderName);
}
}
Hmm...I didn't need to register it as a service in program.cs and it appears to still be working. Will have to learn more about that.

Why is my blazor app leaving so many ports open

I created a .net 6 app using server side Blazor and SignalR. The app was basically a single page with 10 different components. Each component was a client that looked something like this:
#code {
private HubConnection? hubConnection;
private ExampleViewModel data { get; set; } = new ExampleViewModel();
protected override async Task OnInitializedAsync()
{
hubConnection = new HubConnectionBuilder()
.WithUrl(NavigationManager.ToAbsoluteUri("/mainhub"))
.Build();
hubConnection.On<ExampleViewModel>("example", (Data) =>
{
data = Data;
StateHasChanged();
});
await hubConnection.StartAsync();
}
public async ValueTask DisposeAsync()
{
if (hubConnection is not null)
{
await hubConnection.DisposeAsync();
}
}
}
Each component has a "broadcaster" that runs on a timer and makes a call to the database using Mediator and Dapper. Example:
public class ExampleBroadcaster : IDataBroadcaster
{
private readonly IMediator _mediator;
private readonly ILogger<ExampleBroadcaster> _logger;
private readonly IHubContext<MainHub> _mainHub;
private readonly IMemoryCache _cache;
private const string Something = "example";
private Timer _timer;
public ExampleBroadcaster(IHubContext<MainHub> mainHub,
IMediator mediator, ILogger<ExampleBroadcaster> logger,
IMemoryCache cache)
{
_mainHub = mainHub;
_mediator = mediator;
_logger = logger;
_cache = cache;
}
public void Start()
{
_timer = new Timer(BroadcastData, null, 0, 30000);
}
private async void BroadcastData(object? state)
{
ExampleViewModel viewModel;
try
{
if (_cache.TryGetValue(Something, out ExampleViewModel data))
{
viewModel = data;
}
else
{
viewModel = _mediator.Send(new GetExampleData()).Result;
_cache.Set(Something, viewModel, TimeSpan.FromMinutes(10));
}
await _mainHub.Clients.All.SendAsync("example", viewModel);
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
}
}
}
The mediator handler simply uses Dapper to get data from the database:
public class GetExampleData : IRequest<ExampleViewModel>
{
}
public class GetExampleDataHandler : IRequestHandler<GetExampleData, ExampleViewModel>
{
private readonly IDbConnectionFactory _connectionFactory;
private string _storedProcedure = "some sproc name";
public GetExampleDataHandler(IDbConnectionFactory connectionFactory)
{
_connectionFactory = connectionFactory;
}
public async Task<ExampleViewModel> Handle(GetExampleData request, CancellationToken cancellationToken)
{
using (var connection = _connectionFactory.GetReadOnlyConnection())
{
return await connection.QueryFirstAsync<ExampleViewModel>(_storedProcedure, CommandType.StoredProcedure);
}
}
}
This is the main razor page that houses all the individual components:
#code {
private HubConnection? hubConnection;
protected override async Task OnInitializedAsync()
{
try
{
hubConnection = new HubConnectionBuilder()
.WithUrl(NavigationManager.ToAbsoluteUri("/mainhub"))
.Build();
await hubConnection.StartAsync();
await hubConnection.SendAsync("Init");
}
catch(Exception exception)
{
Logger.LogError(exception, exception.Message);
}
}
public async ValueTask DisposeAsync()
{
if (hubConnection is not null)
{
await hubConnection.DisposeAsync();
}
}
}
Finally, the MainHub.cs code:
public class MainHub : Hub
{
IEnumerable<IDataBroadcaster> _broadcasters;
private static bool _started;
public MainHub(IEnumerable<IDataBroadcaster> broadcasters)
{
_broadcasters = broadcasters;
}
public void Init()
{
if (!_started)
{
StartBroadcasting();
_started = true;
}
}
private void StartBroadcasting()
{
foreach (var broadcaster in _broadcasters)
{
broadcaster.Start();
}
}
}
This all worked fine locally, in our dev environment, and our test environment. In production, we found that the app was crashing after a number of hours. According to the server admins, the app is opening 100s or 1000s of ports and leaving them open until the number of allotted ports was hit, causing the app to crash.
What is the issue here? The broadcasters are registered as singletons. This app only runs on one web server.

Why is my hubconnection on but the method is not being fired?

I've set up signalr in my blazor server side application and for some reason this hubconnection is not being triggered, when the hubconnection is on, it completely ignores the BroadcastData method and doesnt even fire it:
private HubConnection hubConnection;
private string _hubUrl;
protected override async Task OnInitializedAsync()
{
string baseUrl = NavigationManager.BaseUri;
_hubUrl = baseUrl.TrimEnd('/') + SignalRHub.HubUrl;
_hubConnection = new HubConnectionBuilder()
.WithUrl(_hubUrl)
.Build();
hubConnection.On<ClientDTO>("BroadcastData", BroadcastData);
await hubConnection.StartAsync();
}
private void BroadcastData(ClientDTO payload)
{
dashboardData = payload;
StateHasChanged();
}
I have everything setup for this to be "working" but clearly it isn't working and I'm completely lost at what could be the problem... Please take a look at what I have so far and see if you can see what's going on:
Startup:
public Startup(IConfiguration configuration)
{
Configuration = configuration;
StartTimer();
}
private void StartTimer()
{
_timer = new System.Timers.Timer();
_timer.Interval = 5000;
_timer.Elapsed += TimerElapsed;
_timer.Start();
}
private void TimerElapsed(Object source, ElapsedEventArgs e)
{
Trigger();
}
public void Trigger()
{
try
{
using (HttpClient client = new HttpClient())
{
//Trigger on elapsed
var response = client.GetAsync(Configuration.GetConnectionString("ApiTriggerURL")).Result;
}
}
catch
{
Console.WriteLine("something terrible has happened...");
}
}
services.AddScoped(typeof(SignalRHub));
services.AddScoped<IHub, SignalRHub>();
services.AddScoped<HttpClient>();
services.AddSignalR();
services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});
public void Configure(IApplicationBuilde app, IWebHostEnvironment env)
{
app.UseResponseCompression();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
endpoints.MapHub<SignalRHub>(SignalRHub.HubUrl);
});
}
appsettings.json: (fyi, the trigger is working, the api endpoint is being hit as it returns a status 200 ok result)
"ConnectionStrings":
{
"ApiTriggerURL": "http://localhost:5000/api/SignalRHub/GetMyData"
}
Then we have my api controller: (here you can see the status 200 ok)
private readonly SignalRHub _signalRHub;
public SignalRHubController(SignalRHub signalRHub)
{
_signalRHub = signalRHub;
}
[HttpGet]
public ObjectResult GetMyData()
{
try
{
Task.WhenAll(_signalRHub.BroadcastData()); // Call hub broadcast method
return this.StatusCode((int)HttpStatusCode.OK, "trigger has been triggered");
}
catch
{
return this.StatusCode((int)HttpStatusCode.InternalServerError, "christ, the ting is broken fam");
}
}
When we look into the _signalRHub.BroadcastData(), we see this:
public class SignalRHub : Hub, IHub
{
private readonly ClientService _clientService;
readonly IHubContext<SignalRHub> _hubContext;
public const string HubUrl = "/chathub"; //this is being read in the startup in the endpoints
public SignalRHub(ClientService clientService, IHubContext<SignalRHub> hubContext)
{
_clientService = clientService;
_hubContext = hubContext;
}
public async Task BroadcastData()
{
var data = _clientService .GetDataAsync().Result;
await _hubContext.Clients.All.SendAsync("BroadcastData", data); //send data to all clients
}
}
And this in turn should basically do this signalrhub every x seconds (depending on timer)
I know my code is a whole load of madness, but please look pass this and help me to understand why this isn't working! Thank you in advance!
Try following:
hubConnection.On<ClientDTO>("BroadcastData", (payload)=>
BroadcastData(payload);
);
Instead of
hubConnection.On<ClientDTO>("BroadcastData", BroadcastData);

list view is unable to display data from cloud firestore

Here is my code behind
IIdentifiable is a interface that has Id as property.
I am getting a nullrefrence exceptions while extracting data.
public abstract class SalonService<T> : ISalonService<T> where T : IIdentifiable
{
public Task<T> GetSalonAsync(string id)
{
var tcs = new TaskCompletionSource<T>();
FirebaseFirestore.Instance
.Collection("salons")
.Document(id)
.Get()
.AddOnCompleteListener(new OnDocumentCompleteListener<T>(tcs));
return tcs.Task;
}
public Task<IList<T>> GetSalonsAsync()
{
var tcs = new TaskCompletionSource<IList<T>>();
var list = new List<T>();
FirebaseFirestore.Instance
.Collection("salons")
.Get()
.AddOnCompleteListener(new OnCollectionCompleteListener<T>(tcs));
return tcs.Task;
}
}
And the service listeners for collection of data
public class OnCollectionCompleteListener<T> : Java.Lang.Object, IOnCompleteListener
where T : IIdentifiable
{
private System.Threading.Tasks.TaskCompletionSource<IList<T>> _tcs;
public OnCollectionCompleteListener(System.Threading.Tasks.TaskCompletionSource<IList<T>> tcs)
{
_tcs = tcs;
}
public void OnComplete(Task task)
{
if (task.IsSuccessful)
{
var docsObj = task.Result;
if (docsObj is QuerySnapshot docs)
{
_tcs.TrySetResult(docs.Convert<T>());
}
}
}
}
Service listener to display document
public class OnDocumentCompleteListener<T> : Java.Lang.Object, IOnCompleteListener
where T : IIdentifiable
{
private TaskCompletionSource<T> _tcs;
public OnDocumentCompleteListener(TaskCompletionSource<T> tcs)
{
_tcs = tcs;
}
public void OnComplete(Task task)
{
if (task.IsSuccessful)
{
var docObj = task.Result;
if (docObj is DocumentSnapshot docRef)
{
_tcs.TrySetResult(docRef.Convert<T>());
return;
}
}
// something went wrong
_tcs.TrySetResult(default);
}
}
The Document extension as well
public static class DocumentReferenceExtensions
{
public static T Convert<T>(this DocumentSnapshot doc) where T : IIdentifiable
{
try
{
var jsonStr = Newtonsoft.Json.JsonConvert.SerializeObject(doc.Data.ToDictionary());
var item = Newtonsoft.Json.JsonConvert.DeserializeObject<T>(jsonStr);
item.Id = doc.Id;
return item;
}
catch (Exception)
{
System.Diagnostics.Debug.WriteLine("EXCEPTION THROWN");
}
return default;
}
public static List<T> Convert<T>(this QuerySnapshot docs) where T : IIdentifiable
{
var list = new List<T>();
foreach (var doc in docs.Documents)
{
list.Add(doc.Convert<T>());
}
return list;
}
}
Please let me know what am I missing? I am stuck here

signalr : after Authentication i get error 400 (Bad Request) in client-side

after Task.CompletedTask i get error 400 (Bad Request) in "client-side"
What am I missing to go through the Authentication phase?
Server:
public class Startup : StartupBase
{
public override void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = options.DefaultScheme;
options.DefaultChallengeScheme = options.DefaultScheme;
})
.AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Headers["Authorization"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken))
{
context.Token = accessToken.ToString().Substring("Token ".Length).Trim();
}
return Task.CompletedTask;
}
};
});
services.AddSignalR();
services.AddSingleton<IMongoDBService, MongoDBService>();
}
public override void Configure(IApplicationBuilder builder, IEndpointRouteBuilder routes, IServiceProvider serviceProvider)
{
builder.UseCors("CorsPolicy");
builder.UseAuthentication();
builder.UseEndpoints(endpoints =>
{
endpoints.MapHub<PrintEventsHub>("/Events/PrintEventsHub");
endpoints.MapHub<NotificationEventsHub>("/Events/NotificationEventsHub");
});
}
}
Hub:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class NotificationEventsHub : Hub//, IPrinterEventsHub
{
private readonly ISession _session;
private readonly ITelegramServices _telegramServices;
private readonly ISaveEventToDBServices _saveEventToDBServices;
private readonly IHubContext<NotificationEventsHub> _Context;
private string ConnectionID;
public NotificationEventsHub(IHubContext<NotificationEventsHub> context,
ISession session,
ITelegramServices telegramServices,
ISaveEventToDBServices saveEventToDBServices)
{
_Context = context;
_session = session;
_telegramServices = telegramServices;
_saveEventToDBServices = saveEventToDBServices;
}
public override Task OnConnectedAsync()
{
ConnectionID = this.Context.ConnectionId;
var connectedUser = Context.User.Identity;
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
return base.OnDisconnectedAsync(exception);
}
public async Task GetNotificationMsg(string jsonStringNotification)
{
}
}
Client:
public static async Task<bool> SendPrinterEventToCloudManager()
{
var SignalRConnectionInfo = new SignalRConnectionInfo();
SignalRConnectionInfo.AccessToken = "test";
string _SignalRUrl =
"https://localhost:44300/Events/NotificationEventsHub";
try
{
HubConnection connection = new HubConnectionBuilder().WithUrl(_SignalRUrl,
o => o.AccessTokenProvider = () => Task.FromResult(SignalRConnectionInfo.AccessToken)).Build();
await connection.StartAsync();
await connection.InvokeAsync("GetNotificationMsg", "testmsg");
return true;
}
catch (Exception ex)
{
var msg = ex.Message;
return false;
}
}
i am try to use JwtBearer token and after i read the value what's next step to aprrove that the user Authentication?

Resources