.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:
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 yourIRootService
IRootService rootService = rootServiceFactory.CreateInstance(connectionString);
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
How to Use Default Serialization in a Custom System.Text.JSON JSONconverter
Owin Security - How to Implement Oauth2 Refresh Tokens
Expression.Lambda and Query Generation at Runtime, Simplest "Where" Example
Microsoft Office Excel Cannot Access the File 'C:\Inetpub\Wwwroot\Timesheet\App_Data\Template.Xlsx'
How to "Await Yield Return Dosomethingasync()"
How to Extend Identityuser with Custom Property
Write Values in App.Config File
Wrong Line Number on Stack Trace
How to Take a Screenshot of a Wpf Control
How to List Directory Contents with Ftp in C#
.Net and Bitmap Not Automatically Disposed by Gc When There Is No Memory Left
When Should I Use the Hashset<T> Type
Mock Httpcontext for Unit Testing a .Net Core MVC Controller
Multi-Tenant with Code First Ef6