Closed fretje closed 2 years ago
In my app, I have already implemented a "quick and dirty" solution by creating a "catch all" notificationhandler, which checks if the INotification
is has a specific interface (IClientNotification<TNotificationMessage>
) and then uses Mapster to map the INotification
to the INotificationMessage
. This way I don't have to implement an INotificationHandler
anymore for every INotification
... but I do still need to create another INotificationMessage
object for every INotifcation
with exactly the same properties:
public interface IClientNotification<TNotificationMessage> : INotification
where TNotificationMessage : INotificationMessage
{
}
public class ClientNotificationHandler<TClientNotification> : INotificationHandler<TClientNotification>
where TClientNotification : INotification
{
private readonly INotificationService _notificationService;
public ClientNotificationHandler(INotificationService notificationService) =>
_notificationService = notificationService;
public Task Handle(TClientNotification clientNotification, CancellationToken cancellationToken)
{
if (clientNotification.GetType().GetInterfaces().FirstOrDefault(i =>
i.IsGenericType &&
i.GetGenericTypeDefinition() == typeof(IClientNotification<>)) is { } clientNotificationInterface &&
clientNotificationInterface.GetGenericArguments()[0] is { } notificationMessageType &&
notificationMessageType.IsAssignableTo(typeof(INotificationMessage)) &&
clientNotification.Adapt(typeof(TClientNotification), notificationMessageType) is INotificationMessage notificationMessage)
{
return _notificationService.SendMessageAsync(notificationMessage, cancellationToken);
}
return Task.CompletedTask;
}
}
It works well... but I think a solution like explained here is what we really need.
That way we can also abstract away the signalr connection much more nicely on the client side, so we don't have to deal with the HubConnection anymore in the application code. Only handling of events using mediatr, or probably even better using Courier on top...
Oh, an about the INotificationMessage
: I've updated that interface to just be a marker interface. There were 2 properties in there:
One was string MessageType
, which in every implementation returned the value nameof(SpecificNotificationMessage)
. Which was then only used in INotificationService
, to us as methodName
parameter in the HubContext.SendAsync
calls.
There, I simply replaced notification.MessageType
with notification.GetType().Name
in every call to hubContext.SendAsync
, which does exactly the same thing (without having to implement a MessageType
string on every INotificationMessage
...
The other one was just string Message
which wasn't actually needed in every INotificationMessage
so no need to be on the Interface. One can easily implement it if it's actually needed for a specific INotificationMessage
.
Maybe this should be in another issue... but I can create a separate PR for this if you want... just let me know!
Hi @fretje this is very nice... Just staying away from SignalR becouse of performance issues... In docs using a seperate SignalR server they suggested..
In my senario many tenant and their branches regularly will use app and their screens always will be open.
what you think about network cost?
I think this would be a nice addition
Hi,
I Am stuck on getting signal r working. Since the documentation is not ready, any basic steps i can follow ?
Is this the url for signalr ? https://localhost:5501/api/v1/notifications
SignalR should be working out of the box... the url is https://localhost:5001/notifications (not under api/v1). This is configured here: https://github.com/fullstackhero/dotnet-webapi-boilerplate/blob/main/src/Infrastructure/Notifications/Startup.cs
Hi, Yes. That I got that working. The main issue is how to publish messages I.e send to all clients from a controller in the host project. Any sample code ?
I don’t to be able to initialize a notification message .
On Mon, 26 Sept 2022, 20:03 fretje, @.***> wrote:
SignalR should be working out of the box... the url is https://localhost:5001/notifications (not under api/v1). This is configured here: https://github.com/fullstackhero/dotnet-webapi-boilerplate/blob/main/src/Infrastructure/Notifications/Startup.cs
— Reply to this email directly, view it on GitHub https://github.com/fullstackhero/dotnet-webapi-boilerplate/issues/440#issuecomment-1258350813, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABCE7FQ37J2RJOFGS7QP3E3WAHJPBANCNFSM5NTZH7BQ . You are receiving this because you commented.Message ID: @.***>
NotificationSender notificationSender = new NotificationSender(_hubContext, _currentTenant);
_notificationMessage.Equals(salesOrder);
await notificationSender.SendToAllAsync(_notificationMessage, new CancellationToken());
how can i add a INotificationMessage ?
You create a class that implements INotificationMessage, and then you can send a new instance of that class with the INotificationSender. You can find an example here:
and here:
There's also the option to implement both IEvent (as in a domain event) and INotificationMessage, and when you then publish that domain event using the IEventPublisher, it will automatically get sent to all clients via the SendEventNotificationToClientsHandler (https://github.com/fullstackhero/dotnet-webapi-boilerplate/blob/main/src/Infrastructure/Notifications/SendEventNotificationToClientsHandler.cs)
Hi @fretje Thank you soo much, it has worked. The only issue i have now is saving changes to local DB. After receiving the requests via a controller and sending notifications, i open a queue to save the data to local DB.
The Mediator.Send method is async, so you have to "await" it...
@fretje still same error
You can't do backgroundwork like that in a controller... a controller object is only created for the duration of a request. So by the time your backgroundthread does its work, the request is finished, and so the controller is disposed, hence the object disposed exception.
You have hangfire to do backgroundwork.
Any code sample to use hangfire in this sample ?
On Wed, 28 Sept 2022, 22:17 fretje, @.***> wrote:
You can't do backgroundwork like that in a controller... a controller object is only created for the duration of a request. So by the time your backgroundthread does its work, the request is finished, and so the controller is disposed, hence the object disposed exception.
You have hangfire to do backgroundwork.
— Reply to this email directly, view it on GitHub https://github.com/fullstackhero/dotnet-webapi-boilerplate/issues/440#issuecomment-1261360650, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABCE7FXGXYKN3FRNVY77ZD3WASKTVANCNFSM5NTZH7BQ . You are receiving this because you commented.Message ID: @.***>
Any code sample to use hangfire in this sample ?
On Wed, 28 Sept 2022, 22:17 fretje, @.***> wrote:
You can't do backgroundwork like that in a controller... a controller object is only created for the duration of a request. So by the time your backgroundthread does its work, the request is finished, and so the controller is disposed, hence the object disposed exception.
You have hangfire to do backgroundwork.
— Reply to this email directly, view it on GitHub https://github.com/fullstackhero/dotnet-webapi-boilerplate/issues/440#issuecomment-1261360650, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABCE7FXGXYKN3FRNVY77ZD3WASKTVANCNFSM5NTZH7BQ . You are receiving this because you commented.Message ID: @.***>
Take a look at the generate brand job in the Application project. And look at the Brands controller and the request/handler used to start the job. There's also some Job services/etc. in the Infrastructure project.
One thing to note with Hangfire is that it's not quite asynchronous, if you find that's an issue, maybe look at Coravel, but I wouldn't worry about it until you need to.
@pyt0xic I checked. I didn't see a hangfire background queue being initiated from any controller
@pyt0xic I checked. I didn't see a hangfire background queue being initiated from any controller
Alright so starting from here: https://github.com/fullstackhero/dotnet-webapi-boilerplate/blob/8d05f1537aefed2ac5c80d51a10e0c8dfb73a802/src/Host/Controllers/Catalog/BrandsController.cs#L54 the GenerateRandomBrandRequestHandler schedules the Job https://github.com/fullstackhero/dotnet-webapi-boilerplate/blob/8d05f1537aefed2ac5c80d51a10e0c8dfb73a802/src/Core/Application/Catalog/Brands/GenerateRandomBrandRequest.cs#L16 Which is implemented here https://github.com/fullstackhero/dotnet-webapi-boilerplate/blob/8d05f1537aefed2ac5c80d51a10e0c8dfb73a802/src/Infrastructure/Catalog/BrandGeneratorJob.cs, using the Hangfire job service https://github.com/fullstackhero/dotnet-webapi-boilerplate/blob/8d05f1537aefed2ac5c80d51a10e0c8dfb73a802/src/Infrastructure/BackgroundJobs/HangfireService.cs#L24
@pyt0xic this worked perfectly. thank you. @fretje thank you too for your insights.
You create a class that implements INotificationMessage, and then you can send a new instance of that class with the INotificationSender. You can find an example here:
and here:
There's also the option to implement both IEvent (as in a domain event) and INotificationMessage, and when you then publish that domain event using the IEventPublisher, it will automatically get sent to all clients via the SendEventNotificationToClientsHandler (https://github.com/fullstackhero/dotnet-webapi-boilerplate/blob/main/src/Infrastructure/Notifications/SendEventNotificationToClientsHandler.cs)
Thanks for the info, but I am really interested in implementing both IEvent and INotificationMessage through SendEventNotificationToClientsHandler, how do I implement both?
Like this:
public class MyEventNotification : IEvent, INotificationMessage
{
}
Like this:
public class MyEventNotification : IEvent, INotificationMessage { }
So something like this should work?
public class HomeCreateNotificationEvent : IEvent, INotificationMessage {
public string Name { get; set; } = default!;
public string? Description { get; set; }
public string Phone { get; set; } = default!;
public string Colour { get; set; } = default!;
public string Adr1 { get; set; } = default!;
public string? Adr2 { get; set; }
public string? Adr3 { get; set; } = default!;
public string Town { get; set; } = default!;
public string? County { get; set; }
public string PostCode { get; set; } = default!;
}
public class CreateHouseRequestHandler : IRequestHandler<CreateHomeRequest, Guid>
{
// Add Domain Events automatically by using IRepositoryWithEvents
private readonly IRepositoryWithEvents<Home> _repository;
private readonly IEventPublisher _publisher;
public CreateHouseRequestHandler(
IRepositoryWithEvents<Home> repository,
IEventPublisher publisher
) => (_repository, _publisher) = (repository, publisher);
public async Task<Guid> Handle(CreateHomeRequest request, CancellationToken cancellationToken)
{
var home = new Home(
request.Name,
request.Description,
request.Phone,
request.Colour,
request.Adr1,
request.Adr2,
request.Adr3,
request.Town,
request.County,
request.PostCode);
await _repository.AddAsync(home, cancellationToken);
await _publisher.PublishAsync(
new HomeCreateNotificationEvent {
Name = home.Name,
Description = home.Description,
Phone = home.Phone,
Colour = home.Colour,
Adr1 = home.Adr1,
Adr2 = home.Adr2,
Adr3 = home.Adr3,
Town = home.Town,
County = home.County,
PostCode = home.PostCode
});
return home.Id;
}
@fretje Thanks for your help, I have something working... I am so pleased!
Hi there, I have successfully implemented Entity Event handling, but I'm encountering difficulties in sending a text message to all clients using Notifications.INotificationMessage. Could you please provide guidance or share a code sample demonstrating how to send a text message from the controller? Thank you
@tvprasad see a previous comment above: https://github.com/fullstackhero/dotnet-webapi-boilerplate/issues/440#issuecomment-1259314218
@fretje Your prompt response is much appreciated. I had already encountered the suggested solution and had put it into action even before seeking assistance here. Interestingly, the same code appears to be functioning correctly today. It seems that a simple reboot may have resolved the issue. Again, thanks the quick reply. ---------
On another note, I have also identified an issue with infinite notification execution on the server.
In the context of Web Assembly and the component, when subscribing to the <NotificationWrapper<BasicNotification>
on the client side, it triggers an endless notification loop on the Blazor server side. Here is the relevant code snippet:
protected override async Task OnInitializedAsync()
{
Courier.SubscribeWeak<NotificationWrapper<BasicNotification>>(async _ =>
{
Snackbar.Add("Received BasicNotification");
StateHasChanged();
});
}
In the Blazor WebApi section, this code snippet is where the issue occurs:
await _notifications.SendToAllAsync(new BasicNotification() { Message = "Hello World!."}, cancellationToken);
The problem appears to stem from the interaction between these code segments, causing the server to execute notifications indefinitely.
For every
INotification
you want to send to the client over SignalR, you have to create anINotificationHandler
for that specific notification, which then calls the hubcontext to send a specificINotificationMessage
(another object alltogether) to the connected clients.In my app I'm building, I have already quite some notifications like that, and was thinking we could make this probably generic in a way you don't have to create separate
INotificationHandler
s andINotificationMessage
s for all theINotification
s you want to be sent to the client as well.In my research around this, I found this article which is looking very close to what we need: https://remibou.github.io/Realtime-update-with-Blazor-WASM-SignalR-and-MediatR/ There's an issue there though pointed out in the comments... and another pointer to an interesting library in this regard: https://github.com/KuraiAndras/MediatR.Courier
I think combining those 2 should be able to give a a nice implementation of something like this...