Resolving services in ServerEvents hooks

Hello,

I would like to know if there is a way how to resolve services inside ServerEventsFeature hooks, or move hooks attachment to another part of code, that is resolved later.

We are using ServerEvents to push notifications to user.

Currently I am registering ServerEvents like this:

public class ConfigureServerEvents : IHostingStartup
{
    public void Configure(IWebHostBuilder builder) => builder
        .ConfigureAppHost(appHost =>
        {
            appHost.Container.AddPlugin(new ServerEventsFeature()
            {
                LimitToAuthenticatedUsers = true,
                NotifyChannelOfSubscriptions = false,
                OnCreated = OnCreated,
                OnSubscribeAsync = async subscription => await OnSubscribeAsync(subscription, appHost),
                OnUnsubscribeAsync = async subscription => await OnUnsubscribeAsync(subscription, appHost),
                OnError = OnError,
                OnDispose = OnDispose,
                OnConnect = OnConnect
            });
            appHost.Container.AddSingleton<INotificationsPublisher, NotificationsPublisher>();
        });
}

I have a class NotificationsPublisher, that basically implements my interface

public interface INotificationsPublisher
{
    public Task PublishNotificationAsync(Notification notification);
}

Inside PublishNotificationAsync, I have a call serverEvents.NotifyUserNameAsync or serverEvents.NotifyChannelAsync. So I am resolving IServerEvents in constructor through DI.

I am doing some database actions and logging inside OnSubscribe and OnUnsubscribe methods. For example, I check if subscribed user has already received notification (through unique installation ID) and if not, we publish notification to user through INotificationsPublisher.PublishToSubscriptionAsync(). Therefore, inside OnSubscribe method, I call

dbConnectionFactory = appHost.Resolve<IWebDbConnectionFactory>();
notificationsPublisher = appHost.Resolve<INotificationsPublisher>();

Now I added some Jobs, because Notifications can be scheduled into the future. I created

public class PublishNotificationsCommand(
    IWebDbConnectionFactory webDbConnectionFactory,
    INotificationsPublisher notificationsPublisher)
    : AsyncCommand

which check for notifications to publish and publishes them to all subscribers in channel if needed.

I registered this command and job like it says in the docs:

public class ConfigureBackgroundJobs : IHostingStartup
{
    public void Configure(IWebHostBuilder builder) => builder
        .ConfigureServices(services =>
        {
            services.AddPlugin(new CommandsFeature());
            services.AddPlugin(new BackgroundsJobFeature());
        }).ConfigureAppHost(afterAppHostInit: appHost =>
        {
            var services = appHost.GetApplicationServices();
            var jobs = services.GetRequiredService<IBackgroundJobs>();
            jobs.RecurringCommand<PublishNotificationsCommand>(Schedule.Interval(TimeSpan.FromMinutes(5)));
        });
}

Here comes the problem, the services.AddPlugin(new BackgroundsJobFeature()); is already registering the class PublishNotificationsCommand, which needs INotificationsPublisher, that is being registered in ConfigureAppHost later in the game. I would move registration of INotificationsPublisher into ConfigureServices, however it has dependency on IServerEvents, so I would need to move ServerEventsFeature registration inside ConfigureServices, but then I don’t know how to make the OnSubscribe hook work with service resolving.

Is there some workaround or way, how to configure hooks later after registering, e.g. register plugin in ConfigureServices but add hooks in ConfigureAppHost?

I have also tried to use NotificationsPublisher(IServiceProvider serviceProvider) and resolve IServerEvents inside PublishNotificationAsync, but for some reason, IServerEvents is not found in IServiceProvider.

Thanks for any ideas.

You need to use ConfigureServices to register dependencies in ASP .NET Core’s IOC.

This is run before the AppHost is constructed so you wont be able to get an instance of the AppHost at time of registration however you can access it from the HostContext.AppHost singleton which is available at runtime when the handlers are fired, e.g:

.ConfigureServices(services => {
    services.AddPlugin(new ServerEventsFeature {
      LimitToAuthenticatedUsers = true,
      NotifyChannelOfSubscriptions = false,
      OnCreated = OnCreated,
      OnSubscribeAsync = async subscription => await OnSubscribeAsync(subscription, HostContext.AppHost),
      OnUnsubscribeAsync = async subscription => await OnUnsubscribeAsync(subscription, HostContext.AppHost),
      OnError = OnError,
      OnDispose = OnDispose,
      OnConnect = OnConnect
    });
    services.AddSingleton<INotificationsPublisher, NotificationsPublisher>();
  })
}).
``
1 Like

Awesome, everything works now as it should. Thank you very much, you helped a lot in the last days!

1 Like