What is meant by 'dependencies' here?
All the parameters that a class has in its constructor, are the dependencies of that class (well, sometimes properties can be marked as dependencies, but let's ignore that for now). In the following class, the dependencies are ILogger
and IDbConnectionFactory
:
public class MyHandler
{
private IConnectionFactory _connectionFactory;
private ILogger _logger;
public MyHandler(
IConnectionFactory connectionFactory,
ILogger logger)
{
_connectionFactory = connectionFactory;
_logger = logger;
}
public void Handle(MyRequest request)
{
// do stuff with the dependencies.
}
}
Note that the parameter MyRequest
of the Handle
method is NOT a dependency of the class, because you don't need it for creating an instance of the class.
What is meant by 'injection' here?
Instead of manually injecting the dependencies:
var myHandler = new MyHandler(new MyConnectionFactory(), new MyLogger());
you could register MyConnectionFactory
and MyLogger
to a "service collection" and have those dependencies injected automatically.
Registering classes to a service collection
In .NET a service collection implements the IServiceCollection
interface. Let's say that you want to have a logger that is the same instance from applicatoin start until shutdown. Then you add it as a "singleton" at startup:
serviceCollection.AddSingleton<ILogger>(new MyLogger());
Now you might think: "But what is that serviceCollection
? Is it a List? A Dictionary?". My answer for now: the service collection is a class that might use a List or a Dictionary or something else internally, but you shouldn't worry about that.
Where to find this service collection? It depends on the type of app you're building. On the page about App startup in ASP.NET Core your can see this line of code:
builder.Services.AddTransient<IStartupFilter, RequestSetOptionsStartupFilter>();
The builder
has a property Services
of the type IServiceCollection
and a IStartupFilter
has been added to the service collection. So, in an ASP.NET Core app, you find the service collection at builder.Services
. Other app types may differ in naming, but you'll probably find it easily in documentation or a sample project.
How to use those services?
At some point during app start, from your the service collection a new class is created that implements the IServiceProvider
interface, which has a method GetService
. In theory one class could implement both interfaces:
public class MyServices : IServiceCollection, IServiceProvider
{
// implement all interface members.
}
but in practice this is (often) not the case.
So, how do we find a property of type IServiceProvider
? When building an ASP.NET Core app, the page has a HttpContext.RequestServices
property that is actually a IServiceProvider
. However, you probably don't need to use that, because if you add a dependency to the constructor of a page, it is automatically injected:
public class IndexModel : PageModel
{
private readonly IMyDependency _myDependency;
public IndexModel(IMyDependency myDependency)
{
_myDependency = myDependency;
}
public void OnGet()
{
_myDependency.Use("Hello!");
}
}
Another way is to use the [FromServices]
attribute in a Get
or Post
method of the page:
public class IndexModel : PageModel
{
public void OnGet([FromServices] IMyDependency myDependency)
{
myDependency.Use("Hello again!");
}
}
What to register and what not?
- Very basic and much-used classes, such as a logger, a database connection factory, an email sender, etc. are nice to have in the service collection. Services may depend on other services, and the service provider handles all the injections for you. Also, whenever the constructor arguments of such a class changes, it doesn't matter, because the interface stays the same.
- High level classes, that are only used on one or two pages, should probably not be added to your service collection. For example, think of a
ProductOverviewQueryHandler
. However, what if that query handler has dependencies? How do I inject them? Read on...
Injecting dependencies in high level classes
This can be done in multiple ways. One way is to use the ActivatorUtilities
class like this anywhere in your page:
ActivatorUtilities.CreateInstance<ProductOverviewQueryHandler>(HttpContext.RequestServices)
...but many people will find that an ugly read.
The way that I find very clean, is to use the NuGet package IGet. Your page class would look like this:
public class IndexModel : PageModel
{
public void OnGet([FromServices] IGet i)
{
var queryHandler = i.Get<ProductOverviewQueryHandler>();
// do something
}
}
or like this
public class IndexModel : PageModel
{
private readonly IGet i;
public IndexModel(IGet iget)
{
i = iget;
}
public void OnGet()
{
var queryHandler = i.Get<ProductOverviewQueryHandler>();
// do something
}
public Task<IActionResult> OnPost(FilledOutForm request)
{
var result = i.Get<OtherHandler>().Handle(request);
// return something
}
}
or create a base class for your pages:
public class PageBase : PageModel
{
private IGet _iget;
private IGet i => _iget ??= HttpContext.RequestServices.GetRequiredService<IGet>();
}
and use it like this:
public class IndexModel : PageBase
{
public void OnGet()
{
var queryHandler = i.Get<ProductOverviewQueryHandler>();
// do something
}
}
Installing IGet
Install IGet via the NuGet Package Manager in Visual Studio and add to the startup of your app:
serviceCollection.AddIGet();
After that, you can use IGet as shown in the examples above.
Further reading