ChatGPT解决这个技术问题 Extra ChatGPT

.NET Core DI, ways of passing parameters to constructor

Having the following service constructor

public class Service : IService
{
     public Service(IOtherService service1, IAnotherOne service2, string arg)
     {    
     }
}

What are the choices of passing the parameters using .NET Core IOC mechanism

services.AddSingleton<IOtherService , OtherService>();
services.AddSingleton<IAnotherOne , AnotherOne>();
services.AddSingleton<IService>(x =>
    new Service(
        services.BuildServiceProvider().GetService<IOtherService>(),
        services.BuildServiceProvider().GetService<IAnotherOne >(),
        ""));

Is there any other way ?

Change your design. Extract the arg into a Parameter Object and inject that.

P
Pang

The expression parameter (x in this case) of the factory delegate is an IServiceProvider.

Use that to resolve the dependencies:

_serviceCollection.AddSingleton<IService>(x => 
    new Service(x.GetRequiredService<IOtherService>(),
                x.GetRequiredService<IAnotherOne>(), 
                ""));

The factory delegate is a delayed invocation. Whenever the type is to be resolved, it will pass the completed provider as the delegate parameter.


yes, this is the way I'm doing it right now, but is there any other way ? more elegant maybe ? I mean It would look a little weird to have other parameters that are registered services. I am looking for something more like register the services normally and only pass the non-service arguments, in this case the arg. Something like Autofac does .WithParameter("argument", "");
No you are building the provider manually, which is bad. The delegate is a delayed invocation. When ever the type is to be resolved it will pass the completed provider as the delegate parameter.
@Nkosi: Have a look at ActivatorUtilities.CreateInstance, its part of the Microsoft.Extensions.DependencyInjection.Abstractions package (so no container specific dependencies)
And what about parameters override at resolve time? Not every param is available at register time. Something similar as docs.autofac.org/en/stable/resolve/parameters.html
Have a look at docs.microsoft.com/en-us/dotnet/core/extensions/… for service registering methods,
S
Syntax

The recommended way to achieve this is to use the Options pattern - note that this applies to any .NET Core/5 application, not just ASP.NET Core. But there are use cases where it's impractical (e.g. when parameters are only known at runtime, not at startup/compile-time) or you need to dynamically replace a dependency.

It's very useful when you need to replace a single dependency (be it a string, integer or another type of dependency) or when using a 3rd-party library which accepts only string/integer parameters and you require runtime parameters.

You could try CreateInstance<T>(IServiceProvider, Object[]) as a shortcut rather than resolving every single dependency manually:

_serviceCollection.AddSingleton<IService>(x => 
    ActivatorUtilities.CreateInstance<Service>(x, "");
);

The parameters to pass to your service's constructor (the object[] parameter to CreateInstance<T>/CreateInstance) allows you to specify parameters that should be injected directly, as opposed to resolved from the service provider. They are applied from left to right as they appear (i.e. first string will be replaced with the first string-typed parameter of the type to be instantiated).

ActivatorUtilities.CreateInstance<Service> is used in many places to resolve service and replace one of the default registrations for this single activation.

For example, if you have a class named MyService, and it has IOtherService, ILogger<MyService> as dependencies and you want to resolve the service but replace the default service of IOtherService (say it's OtherServiceA) with OtherServiceB, you could do something like:

myService = ActivatorUtilities.CreateInstance<Service>(serviceProvider,
    new OtherServiceB());

Then the first parameter of IOtherService will get OtherServiceB injected, rather than OtherServiceA - but the remaining parameters will come from the service provider.

This is helpful when you have many dependencies and want just to treat a single one specially (i.e. replace a database-specific provider with a value configured during the request or for a specific user, something you only know at runtime and/or during a request - not when the application is built/started).

If performance is a concern, you can use ActivatorUtilities.CreateFactory(Type, Type[]) to create a factory method instead. GitHub reference and benchmark.

This is useful when the type is resolved very frequently (such as in SignalR and other high request scenarios). Basically, you'd create an ObjectFactory via

var myServiceFactory = ActivatorUtilities.CreateFactory(typeof(MyService), new object[] { typeof(IOtherService), });

then cache it (as a variable etc.) and invoke it where needed:

MyService myService = myServiceFactory(serviceProvider, myServiceOrParameterTypeToReplace);

This all works perfectly with primitive types too - here's an example I tested with:

class Program
{
    static void Main(string[] args)
    {
        var services = new ServiceCollection();
        services.AddTransient<HelloWorldService>();
        services.AddTransient(p => p.ResolveWith<DemoService>("Tseng", "Stackoverflow"));

        var provider = services.BuildServiceProvider();

        var demoService = provider.GetRequiredService<DemoService>();

        Console.WriteLine($"Output: {demoService.HelloWorld()}");
        Console.ReadKey();
    }
}

public class DemoService
{
    private readonly HelloWorldService helloWorldService;
    private readonly string firstname;
    private readonly string lastname;

    public DemoService(HelloWorldService helloWorldService, string firstname, string lastname)
    {
        this.helloWorldService = helloWorldService ?? throw new ArgumentNullException(nameof(helloWorldService));
        this.firstname = firstname ?? throw new ArgumentNullException(nameof(firstname));
        this.lastname = lastname ?? throw new ArgumentNullException(nameof(lastname));
    }

    public string HelloWorld()
    {
        return this.helloWorldService.Hello(firstname, lastname);
    }
}

public class HelloWorldService
{
    public string Hello(string name) => $"Hello {name}";
    public string Hello(string firstname, string lastname) => $"Hello {firstname} {lastname}";
}

// Just a helper method to shorten code registration code
static class ServiceProviderExtensions
{
    public static T ResolveWith<T>(this IServiceProvider provider, params object[] parameters) where T : class => 
        ActivatorUtilities.CreateInstance<T>(provider, parameters);
}

Prints

Output: Hello Tseng Stackoverflow


This is also how ASP.NET Core instantiates the Controllers by default ControllerActivatorProvider, they are not directly resolved from the IoC (unless .AddControllersAsServices is used, which replaces the ControllerActivatorProvider with ServiceBasedControllerActivator
Worth noting that using this outside of the DI framework (The usage in this question looks fine to me) would be the service locator anti-pattern.
@Wouter: No you couldn't. The constructor in question has 3 constructor parameters. When you new it, you need to pass every single constructor parameter. With ActivatorUtils, you pass only the parameters that should NOT come from the container. This is especially useful when the string/int parameters are determined at runtime, not at start time (which is where DI container is configured and can't change afterwards). The code is for demonstration, to show, that you can inject runtime parameters (or even service instances) while the rest comes from DI container
With the main point being to avoid to have to provider.GetRequiredService<T>() every single dependency of the given class, which also breaks your code when you add an additional parameter to the constructor
You'd usually make some kind of factory, such as MyServiceFactory, that gets IServiceProvider injected, and resolve it from there (ActivatorUtils need ServiceProvder to resolve the dependencies). If the parameters can be obtained from the services (i.e. IHttpContextAccessor, you could call the factory method in the registration: services.AddScoped<MyService>(provider => provider.GetRequiredService<MyServiceFactory>().Create())
P
Pang

If you feel uncomfortable with newing the service, you could use the Parameter Object pattern.

So extract the string parameter into its own type

public class ServiceArgs
{
   public string Arg1 {get; set;}
}

And the constructor will now look like

public Service(IOtherService service1, 
               IAnotherOne service2, 
               ServiceArgs args)
{

}

And the setup

_serviceCollection.AddSingleton<ServiceArgs>(_ => new ServiceArgs { Arg1 = ""; });
_serviceCollection.AddSingleton<IOtherService , OtherService>();
_serviceCollection.AddSingleton<IAnotherOne , AnotherOne>();
_serviceCollection.AddSingleton<IService, Service>();

The first benefit is if you need to change the Service constructor and add new services to it, then you don't have to change the new Service(... calls. Another benefit is the setup is a bit cleaner.

For a constructor with a single parameter or two, this might be too much though.


It would be more intuitive for complex parameters to use the Options pattern and is the recommended way for options pattern, however is less suitable for parameters only know at runtime (i.e. from a request or a claim)
A
Alamgir

You can inject dependencies with this process also

_serviceCollection.AddSingleton<IOtherService , OtherService>();
_serviceCollection.AddSingleton<IAnotherOne , AnotherOne>();
_serviceCollection.AddSingleton<IService>(x=>new Service( x.GetService<IOtherService>(), x.GetService<IAnotherOne >(), "" ));