Daily Knowledge Drop
Constructor and minimal api parameters
can have default values
, allowing for dependency injection to work - while not actually working. Specifying a default value allows for the application to run with a default implementation even if the services has not been registered with the dependency injection container.
This is not something one would usually do as a standard practice, but in specific use cases it does have a place.
Example
Setup
In the below examples, we have an interface, and a couple of implementations:
The interface:
public interface IDependencyInterface
{
string GetName();
}
And the implementations:
// a default implementation
public class DefaultImplementation : IDependencyInterface
{
public string GetName()
{
return nameof(DefaultImplementation);
}
}
// and another implementation
public class OtherImplementation : IDependencyInterface
{
public string GetName()
{
return nameof(OtherImplementation);
}
}
Injection
Usually to make use of the functionality provided by the interface and the associated implementation, the service is registered with the dependency injection container:
// Depending on the implementation the application requires:
builder.Services.AddTransient<IDependencyInterface, DefaultImplementation>();
// OR
builder.Services.AddTransient<IDependencyInterface, OtherImplementation>();
and then is injected into the relevent constructor, or minimal api delegate in the below sample:
// explicitly tell it to get the IDependencyInterface implementation
// from the DI service collection
app.MapGet("/name", ([FromServices]IDependencyInterface injected) =>
{
return injected.GetName();
});
DI Assumption
In the above setup, the runtime assumes that the necessary registrations with the dependency injection container have take place. If neither of the following registrations are done:
builder.Services.AddTransient<IDependencyInterface, DefaultImplementation>();
builder.Services.AddTransient<IDependencyInterface, OtherImplementation>();
when trying to inject IDependencyInterface, the following error will be experienced:
InvalidOperationException: No service for type 'IDependencyInterface' has been registered.
We've told the runtime and dependency injection container to inject the IDependencyInterface implementation, but have not made it aware of any implementations!
Default value
Generally if in control of the entire dependency injection container registration, one should ensure that the required registrations are performed, and the error is resolved.
However, in some cases this might not be possible - for example if developing a library package, there is no direct control over what the developer configures with the dependency injection container.
As a developer of the library package, one could just allow the exception to occur, which directs the developer to configure the dependency injection container correctly. Another, arguably more developer friendly technique, is to automatically set a default implementation if one is not explicitly set
. This does require a few updates to the code:
// make the parameter nullable, the default value will be null.
// In the case of a constructor (vs this minimal api delegate)
// the value can explicitly be set to null if desired:
// IDependencyInterface? injected = null
app.MapGet("/name", ([FromServices]IDependencyInterface? injected) =>
{
// if it is null, set it to the default implementation
injected ??= new DefaultImplementation();
return injected.GetName();
});
Two changes are made to code:
- Set the parameter as
nullable
using the?
operator - Instantiate the parameter to the
default implementation
if the value of the parameter is null
Now if no implementation is registered
for IDependencyInterface, the application will still function and use the default implementation
specified.
Notes
As mentioned, this is not a practice which is generally recommended - however in the case of developing an external library (which requires a dependency), there is no guarantee the host application has injected the required dependencies. By making use of default values
the library will still function with default configuration, but allows for specific implementations to be overwritten
by the developer if required.
References
Daily Drop 158: 12-09-2022
At the start of 2022 I set myself the goal of learning one new coding related piece of knowledge a day.
It could be anything - some.NET / C# functionality I wasn't aware of, a design practice, a cool new coding technique, or just something I find interesting. It could be something I knew at one point but had forgotten, or something completely new, which I may or may never actually use.
The Daily Drop is a record of these pieces of knowledge - writing about and summarizing them helps re-enforce the information for myself, as well as potentially helps others learn something new as well.