.Net Core Di, Ways of Passing Parameters to Constructor

.NET Core DI, ways of passing parameters to constructor

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.

Dependency injection, inject with parameters

You can either provide a delegate to manually instantiate your cache provider or directly provide an instance:

services.AddSingleton<ICacheProvider>(provider => new RedisCacheProvider("myPrettyLocalhost:6379"));

services.AddSingleton<ICacheProvider>(new RedisCacheProvider("myPrettyLocalhost:6379"));

Please note that the container will not explicitly dispose of manually instantiated types, even if they implement IDisposable. See the ASP.NET Core doc about Disposal of Services for more info.

.Net Core DI pass runtime parameter into constructor of service and also into 1-n of its' subservices

One way to do that is creating a scoped service to provide access to the metadata:

public interface IMetadataAccessor
{
IEnumerable<string> Metadata { get; set; }
}

public class MetadataProcessor : IMetadataAccessor
{
public IEnumerable<string> Metadata { get; set; }
}

Register the service as scoped:

serviceCollection.AddScoped<IMetadataAccessor, MetadataProcessor>();

Change your process and generator classes to have IMetadataAccessor injected via constructor, and read metadata like this:

public IEnumerable<string> Metadata => _metadataAccessor.Metadata;

Change your factory to instantiate process within a scope:

public Processor CreateProcessor(IEnumerable<string> metadata)
{
// Resolve services within a child scope
using (var scope = _services.CreateScope())
{
// Resolve the accessor service and set metadata
var accessor = scope.ServiceProvider.GetRequiredService<IMetadataAccessor>();
accessor.Metadata = metadata;

// Within the current scope, there is only one IMetadataAccessor.
// So both process and generator will be getting same accessor instance via the constructor.
// If we set metadata to the instance, all services can get the value.
var process = scope.ServiceProvider.GetRequiredService<Process>();
return process;
}
}

Passing DI object to constructor of new class

If you are not making any explicit reference calls to the context within IndexModel then only inject the DbManager.

private readonly IDbManager manager;

public IndexModel(IDbManager manager) {
this.manager = manager;
}

public void OnGet() {
manager.GetStuffFromDb();

//...
}

The context will be injected into the manager when being resolved, provided it (the context) was also registered in the composition root

//...

builder.Services.AddScoped<IDbManager, DbManager>();
builder.Services.AddDbContext<AppDbContext>(....);

//...

Reference Explicit Dependencies Principle

.net core dependency injection with parameters on constructor

Anywhere where you are calling "new" to create an object isn't great for doing constructor DI from top to bottom. DI isn't suitable when you want to pass in parameters into constructors.

As others have alluded to, the best way is to create a factory. It might look something like this.

public class Person 
{
private readonly ISomeService _service;
private readonly string _phoneNumber;
public Person (ISomeService service, string phoneNumber)
{
_service = service;
_phoneNumber = phoneNumber;
}

public string PhoneNumber {get { return _phoneNumber; } }
public string Gender {get { return _service.GetGenderFromDb(); } }
}

public class PersonFactory : IPersonFactory
{
private readonly ISomeService _someService;

public PersonFactory(ISomeService someService)
{
_someService = someService;
}

public GetPerson(string phoneNumber)
{
return new Person(_someService, phoneNumber);
}
}

Now when you want to create a person, instead you would inject in an instance of IPersonFactory, and call GetPerson on it.

Furthermore, you may find that you want your models to be more plain and the factory to do most of the heavy lifting. I see that Gender is coming from the database at the moment, so you may change it to look more like the following :

public class Person 
{
public Person (string gender, string phoneNumber)
{
Gender = gender;
PhoneNumber = phoneNumber;
}

public string PhoneNumber {get; private set; }
public string Gender {get; private set;}
}

public class PersonFactory : IPersonFactory
{
private readonly ISomeService _someService;

public PersonFactory(ISomeService someService)
{
_someService = someService;
}

public GetPerson(string phoneNumber)
{
var gender = _someService.GetGenderFromDb();
return new Person(gender, phoneNumber);
}
}

Now your Person class doesn't have any details about where it gets a Gender from, and the factory works out how to create a Person model each time.

How can I pass a runtime parameter as part of the dependency resolution?

To pass a runtime parameter not known at the start of the application, you have to use the factory pattern. You have two options here:

  1. factory class (similar to how IHttpClientFactory is implemented)

     public class RootService : IRootService
    {
    public RootService(INestedService nested, IOtherService other)
    {
    // ...
    }
    }

    public class RootServiceFactory : IRootServiceFactory
    {
    // in case you need other dependencies, that can be resolved by DI
    private readonly IServiceProvider services;

    public RootServiceFactory(IServiceProvider services)
    {
    this.services = services;
    }

    public IRootService CreateInstance(string connectionString)
    {
    // instantiate service that needs runtime parameter
    var nestedService = new NestedService(connectionString);

    // note that in this example, RootService also has a dependency on
    // IOtherService - ActivatorUtilities.CreateInstance will automagically
    // resolve that dependency, and any others not explicitly provided, from
    // the specified IServiceProvider
    return ActivatorUtilities.CreateInstance<RootService>(services,
    new object[] { nestedService, });
    }
    }

    and inject IRootServiceFactory instead of your IRootService

     IRootService rootService = rootServiceFactory.CreateInstance(connectionString);
  2. factory method

     services.AddTransient<Func<string,INestedService>>((provider) => 
    {
    return new Func<string,INestedService>(
    (connectionString) => new NestedService(connectionString)
    );
    });

    and inject the factory method into your service instead of INestedService

     public class RootService : IRootService
    {
    public INestedService NestedService { get; set; }

    public RootService(Func<string,INestedService> nestedServiceFactory)
    {
    NestedService = nestedServiceFactory("ConnectionStringHere");
    }

    public void DoSomething()
    {
    // implement
    }
    }

    or resolve it per call

     public class RootService : IRootService
    {
    public Func<string,INestedService> NestedServiceFactory { get; set; }

    public RootService(Func<string,INestedService> nestedServiceFactory)
    {
    NestedServiceFactory = nestedServiceFactory;
    }

    public void DoSomething(string connectionString)
    {
    var nestedService = nestedServiceFactory(connectionString);

    // implement
    }
    }

.net core dependency injection, inject with parameters

You are creating your own IServiceProvider in your "Initilize Service Collection" region, which is a different instance of IServiceProvider to the one that you're using here:

var _emailService = sp.GetRequiredService<IEmailService>();

The registrations you've made in your region are simply being thrown away. In order to resolve this, you can just pull those registrations into your HostBuilder.ConfigureServices callback function:

.ConfigureServices((hostContext, services) =>
{
services.AddLogging();
services.AddHostedService<LifetimeEventsHostedService>();
services.AddHostedService<TimedHostedService>();
services.AddEntityFrameworkSqlServer();
services.AddDbContext<EmailDBContext>(option => option.UseSqlServer(configuration.GetConnectionString("connection_string")));
services.AddSingleton<IEmailConfiguration>(configuration.GetSection("EmailConfiguration").Get<EmailConfiguration>());
services.AddScoped<ISMTPService, SMTPService>();
services.AddScoped<IEmailService, EmailService>();
services.AddScoped<IRabbitMQPersistentConnection, RabbitMQPersistentConnection>(sp =>
{
var logger = sp.GetRequiredService<ILogger<RabbitMQPersistentConnection>>();
var _emailService = sp.GetRequiredService<IEmailService>();
var _rabbitMQConfiguration = configuration.GetSection("RabbitMQConfiguration").Get<RabbitMQConfiguration>();

var factory = new ConnectionFactory()
{
HostName = _rabbitMQConfiguration.EventBusConnection
};

if (!string.IsNullOrEmpty(_rabbitMQConfiguration.EventBusUserName))
{
factory.UserName = _rabbitMQConfiguration.EventBusUserName;
}

if (!string.IsNullOrEmpty(_rabbitMQConfiguration.EventBusPassword))
{
factory.Password = _rabbitMQConfiguration.EventBusPassword;
}

return new RabbitMQPersistentConnection(logger, factory, _emailService);
});
})


Related Topics



Leave a reply



Submit