4

I have a transient service that has to do async initialization (connecting to a different server). It exposes a property public Task InitTask which one can wait till the service is initlized. It also exposes subservices that can be savely accessed after the InitTask has finished.

public class Service
{
    public Task InitTask { get; }

    public ISubService1 SubService1 { get; }
    public ISubService2 SubService2 { get; }
    public ISubService3 SubService3 { get; }
}

All functionallity that the connection to the other server provides is capulated by these sub services. Normally I would inject the main service and then wait for it to be finished initlaization and then use one of these sub services. But I would like to just inject these subservices.

At first I tried

services.AddTransient<Service>()
    .AddTransient(provider =>
    {
        var server = provider.GetService<Service>();
        return server.SubService1;
    })
    .AddTransient(provider =>
    {
        var server = provider.GetService<Service>();
        return server.SubService2;
    })
    .AddTransient(provider =>
    {
        var server = provider.GetService<Service>();
        return server.SubService3;
    });

but this has the obvious problem of not awaiting the InitTask of the main service.

Then (since the sub-services are exposed via interfaces) I tried to code wrapper classes for the sub-services like

public class WrapperSubService1 : ISubService1
{
    private readonly Service server;

    public WrapperSubService1(Service server)
    {
        this.server = server;
    }

    private async ValueTask<ISubService1> GetSubService1Async()
    {
        await server.InitTask
        return server.SubService1;
    }

    // interface implementations

    public async Task<Example> GetExampleAsync(...) 
    {
        var subService1 = await this.GetSubService1Async();

        return await subService1.GetExampleAsync(...);
    }

    // many more (also some events and properties)
}

and do at startup

services.AddTransient<Service>()
    .AddTransient<ISubService1, WrapperSubService1>()
    .AddTransient<ISubService2, WrapperSubService2>()
    .AddTransient<ISubService3, WrapperSubService3>();

but this has also an obvious flaw: code duplication.

What I would wish for would be something like:

services.AddTransient<Service>()
    .AddTransient(async provider =>
    {
        var server = provider.GetService<Service>();
        await server.InitTask;
        return server.SubService1;
    })
    .AddTransient(async provider =>
    {
        var server = provider.GetService<Service>();
        await server.InitTask;
        return server.SubService2;
    })
    .AddTransient(async provider =>
    {
        var server = provider.GetService<Service>();
        await server.InitTask;
        return server.SubService3;
    });

but this then exposes just Task<SubService1> for injection.

Any ideas?

4

3 Answers 3

1

The only solutions I've found for this kind of situation is to either:

  1. Make your initialization synchronous. On ASP.NET in particular, I think this is a fine option, since it makes sense to not receive requests until the initialization is complete.
  2. Have a separate "schema deployer" project that does that kind of initialization (creating queues/tables) before your service is even deployed. This is the solution I tend to use in modern apps.
Sign up to request clarification or add additional context in comments.

Comments

0

I think you can actually make use of explicit interface implementations in this case:

Change the Service class to

public class Service : ISubService1, ISubService2, ISubService3
{
    public Task InitTask { get; }

    public ISubService1 SubService1 { get; }
    public ISubService2 SubService2 { get; }
    public ISubService3 SubService3 { get; }

....


    private async ValueTask<ISubService1> GetSubService1Async()
    {
        await server.InitTask
        return server.SubService1;
    }

    // interface implementations

    async Task<Example> ISubService1.GetExampleAsync(...) 
    {
        var subService1 = await this.GetSubService1Async();

        return await subService1.GetExampleAsync(...);
    }

....

    async Task<Example> ISubService2.GetExampleAsync(...) 
    {
        var subService1 = await this.GetSubService2Async();

        return await subService1.GetExampleAsync(...);
    }
// and so on
}

Comments

0

I do think exposing Task<SubService1> is fine.

As for the code that resolves the service, it looks like:

var subService1 = await serviceProvider.GetService<Task<SubService1>>();
var result = await subService1.GetExampleAsync();

It looks graceful. Or you can write an extension method like:

public static Task<T> GetServiceAsync<T>(this IServiceProvider provider) => provider.GetService<Task<T>>();

Then

var subService1 = await serviceProvider.GetServiceAsync<SubService1>();

would be more clear.

Additionally, the design of Microsoft.Extensions.DependencyInjection based default DI does not work like an object container, but just an IoC interface (This is different from the well-known DI Containers like autofac). We are not supposed to retrieve every single object directly from the provider, even though it may look more graceful.

Therefore getting a subservice directly from the main service subService1 = provider.GetService<MainService>().GetService1Async(); would be the recommended way.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.