0

I am looking to leverage off .Net Core taking care of the lifetime of services which are the same as the whole app and I want to inject them into another hosted service. I know that I could just implement the start/stop of the listeners myself in the Server's start/stop. But it feels unnecessary if I could the below scenario to work.

I would prefer to get it down to a single line for the registration. I was playing around with creating Extension methods to the IServiceCollection.

public class Server: IHostedService
{
    public Server(IEnumerable<IConnectionListener> listeners)
    {
        foreach(var l in listeners) 
        {
            l.Connected += HandleConnection;
        }
    }

    private void HandleConnection(object src, Foo foo) { }

    public async Task StartAsync(CancellationToken ct)
    {}

    public async Task StopAsync(CancellationToken ct)
    {}
}


public interface IConnectionListener
{
    event ConnectionHandler Connected;
}

public class ConnectionListener: BackgroundService, IConnectionListener
{
    public async Task ExecuteAsync(CancellationToken ct)
    {
         // Open TcpListener and register with the ct to stop the listener.
    }
}

public class SslConnectionListener: BackgroundService, IConnectionListener
{
    public async Task ExecuteAsync(CancellationToken ct)
    {
         // Open TcpListener and register with the ct to stop the listener.
         // Add some extra SSL stuff
    }
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureServices((hc, services) => 
        {
            // This appears to work. But I have concerns about whether the life times will truly
            // be singleton and automatic disposal of the objects (having used the factory, I do
            // want the automatic disposal by the container).
            var listener = new ConnectionListener();
            var sslListener = new SslConnectionListener();
            services.AddSingleton<IConnectionListener>(sp => listener);
            services.AddSingleton<IConnectionListner>(sp => sslListener);

            services.AddHostedService(sp => listener);
            services.AddHostedService(sp => sslListener);

            // This doesn't work
            services.AddHostedService<SslConnectionListener>();
            services.AddHostedService<ConnectionListener>()
            services.AddHostedService<Server>();
        }
Nkosi
  • 235,767
  • 35
  • 427
  • 472
uriDium
  • 13,110
  • 20
  • 78
  • 138
  • Also I didn't really get the actual conditions when you might want to dispose your services. Could you please clarify on that? – Andriy Shevchenko Oct 05 '21 at 12:01
  • I also wonder why would you need to run `ConnectionListener` and `SslConnectionListener` twice: first when you inject it via `.AddHostedService` and second time when you do `foreach (var listener in listeners)` as soon as they will be firing same events twice I presume – Andriy Shevchenko Oct 05 '21 at 12:06
  • @AndriyShevchenko I don't think I am "running" them twice. In the server I am just subscribing to their Connected event so that I can have one instance of the Server handling all connection types. Surely the Application would be the one "running" them? – uriDium Oct 05 '21 at 12:12
  • I understood. By the way though, first approach is good to me. To make it better, implement `IDisposable`: https://stackoverflow.com/questions/40844151/when-are-net-core-dependency-injected-instances-disposed. I – Andriy Shevchenko Oct 05 '21 at 12:17

2 Answers2

1

You could consider this approach

//register the instances
services.AddSingleton<ConnectionListener>();
services.AddSingleton<SslConnectionListener>();
//register their abstractions
services.AddSingleton<IConnectionListener>(sp => sp.GetService<ConnectionListener>());
services.AddSingleton<IConnectionListener>(sp => sp.GetService<SslConnectionListener>());
//register hosted services.
services.AddHostedService(sp => sp.GetService<ConnectionListener>());
services.AddHostedService(sp => sp.GetService<SslConnectionListener>());
services.AddHostedService<Server>();

This way the container manages the entire lifetime of the created instances.

This could be neatly wrapped in an extension method to simplify the call.

Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • Registering first the instances and then the abstractions is something I didn't think of. I was having trouble turning my first example into an extension method because I was trying to get the Service via the interface which worked the first time, but the second time it is technically just overriding the first registration and it felt like it was working by luck rather than design. – uriDium Oct 05 '21 at 13:42
  • The second call overrides the first only for single instance injection. The `Server` is expecting `IEnumerable` so it will work as expected there. – Nkosi Oct 05 '21 at 13:45
0

The container is not creating this instance:

var listener = new ConnectionListener();

If you want it as a singleton, just use the resolver because you're already at the root:

services.AddSingleton<IConnectionListener, ConnectionListener>();
services.AddHostedService(resolver => { return resolver.GetService<IConnectionListener>(); });

Guaranteed to be a singleton

GH DevOps
  • 305
  • 1
  • 11