Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce IServiceActivator<T> #79458

Open
1 task done
jez9999 opened this issue Dec 9, 2022 · 7 comments
Open
1 task done

Introduce IServiceActivator<T> #79458

jez9999 opened this issue Dec 9, 2022 · 7 comments
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-Extensions-DependencyInjection
Milestone

Comments

@jez9999
Copy link

jez9999 commented Dec 9, 2022

Is there an existing issue for this?

  • I have searched the existing issues

Is your feature request related to a problem? Please describe the problem.

With ASP.NET's dependency injection, I have to specify my dependencies in the constructor. This is not possible when I'm injecting a dependency some of whose constructor arguments I only know during a class's method call. For example, I might have a GameTurnManager class that takes gameState to determine the state of the game's play field to use when performing various operations on the game's state. However, my API call might specify the game ID to use as part of the API call:

public async Task EndTurn(int gameId, int playerId) {
    var gameState = getGameState(gameId);
    var turnManager = new GameTurnManager(...ILogger<GameTurnManager>..., gameState, playerId);
    turnManager.ValidateTurn();
    turnManager.EndTurn();
    var serializedState = turnManager.SerializeState();
    // ...
}

Because some of the arguments to the constructor are only known during the method call, it's not possible to specify them in the parent class's constructor and therefore not possible to pull in GameTurnManager through constructor DI.

Describe the solution you'd like

ActivatorUtilities.CreateInstance<GameTurnManager>(IServiceProvider, gameState, playerId) can be used during the method to get the GameTurnManager from DI whilst providing certain arguments at the time of instance creation. However, this does hide the GameTurnManager dependency that the parent class has. It would be nice to expose that dependency without having to instantiate the dependency until it's needed and the extra arguments are known.

Why not introduce an interface IServiceActivator<T> which can be used for precisely that? When a constructor took IServiceActivator<T>, the IServiceProvider would provide an instance of a class that can be used to activate T and provide extra paramaters not provided by the IServiceProvider. This would give the benefit that the dependency on GameTurnManager would be exposed:

public class MyApi {
    private readonly IServiceActivator<GameTurnManager> _gtmActivator;

    public MyApi(IServiceActivator<GameTurnManager> gtmActivator) {
        _gtmActivator = gtmActivator;
    }

    public async Task EndTurn(int gameId, int playerId) {
        var gameState = getGameState(gameId);
        var turnManager = _gtmActivator.CreateInstance(gameState, playerId);
        turnManager.ValidateTurn();
        turnManager.EndTurn();
        var serializedState = turnManager.SerializeState();
        // ...
    }
}

It would also allow unit tests to create an IServiceActivator<IGameTurnManager> which could provide a mock GameTurnManager if needed.

@davidfowl davidfowl transferred this issue from dotnet/aspnetcore Dec 9, 2022
@ghost ghost added the untriaged New issue has not been triaged by the area owner label Dec 9, 2022
@ghost
Copy link

ghost commented Dec 9, 2022

Tagging subscribers to this area: @dotnet/area-extensions-dependencyinjection
See info in area-owners.md if you want to be subscribed.

Issue Details

Is there an existing issue for this?

  • I have searched the existing issues

Is your feature request related to a problem? Please describe the problem.

With ASP.NET's dependency injection, I have to specify my dependencies in the constructor. This is not possible when I'm injecting a dependency some of whose constructor arguments I only know during a class's method call. For example, I might have a GameTurnManager class that takes gameState to determine the state of the game's play field to use when performing various operations on the game's state. However, my API call might specify the game ID to use as part of the API call:

public async Task EndTurn(int gameId, int playerId) {
    var gameState = getGameState(gameId);
    var turnManager = new GameTurnManager(...ILogger<GameTurnManager>..., gameState, playerId);
    turnManager.ValidateTurn();
    turnManager.EndTurn();
    var serializedState = turnManager.SerializeState();
    // ...
}

Because some of the arguments to the constructor are only known during the method call, it's not possible to specify them in the parent class's constructor and therefore not possible to pull in GameTurnManager through constructor DI.

Describe the solution you'd like

ActivatorUtilities.CreateInstance<GameTurnManager>(IServiceProvider, gameState, playerId) can be used during the method to get the GameTurnManager from DI whilst providing certain arguments at the time of instance creation. However, this does hide the GameTurnManager dependency that the parent class has. It would be nice to expose that dependency without having to instantiate the dependency until it's needed and the extra arguments are known.

Why not introduce an interface IServiceActivator<T> which can be used for precisely that? When a constructor took IServiceActivator<T>, the IServiceProvider would provide an instance of a class that can be used to activate T and provide extra paramaters not provided by the IServiceProvider. This would give the benefit that the dependency on GameTurnManager would be exposed:

public class MyApi {
    private readonly IServiceActivator<GameTurnManager> _gtmActivator;

    public MyApi(IServiceActivator<GameTurnManager> gtmActivator) {
        _gtmActivator = gtmActivator;
    }

    public async Task EndTurn(int gameId, int playerId) {
        var gameState = getGameState(gameId);
        var turnManager = _gtmActivator.CreateInstance(gameState, playerId);
        turnManager.ValidateTurn();
        turnManager.EndTurn();
        var serializedState = turnManager.SerializeState();
        // ...
    }
}

It would also allow unit tests to create an IServiceActivator<IGameTurnManager> which could provide a mock GameTurnManager if needed.

Author: jez9999
Assignees: -
Labels:

area-Extensions-DependencyInjection

Milestone: -

@davidfowl
Copy link
Member

This isn't an API proposal, so I don't know the shape of IServiceActivator<T>, can you elaborate? This sounds like generic Func<..., TService> support in the container.

@jez9999
Copy link
Author

jez9999 commented Dec 9, 2022

Yes, I suppose it would be Func<..., TService> support (if the ... means you can pass certain constructor params that are not provided by the DI framework) - does that exist currently?

The other thing is that using the Func<..., TService> syntax would mean you'd need to specify the all "in" types. You couldn't just have the equivalent of params object[] parameters at the end of a method to allow a varying number of parameters to be supplied to the constructor, could you?

@jez9999
Copy link
Author

jez9999 commented Dec 9, 2022

Maybe a better name would be IServiceFactory<T>?

@JanEggers
Copy link

have you tried ActivatorUtilities ?

@jez9999
Copy link
Author

jez9999 commented Dec 11, 2022

My original post talks about ActivatorUtilities.

@JanEggers
Copy link

a sorry missed that.

@steveharter steveharter added api-suggestion Early API idea and discussion, it is NOT ready for implementation and removed untriaged New issue has not been triaged by the area owner labels Dec 21, 2022
@steveharter steveharter added this to the Future milestone Dec 21, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-Extensions-DependencyInjection
Projects
None yet
Development

No branches or pull requests

4 participants