13

In some IoC containers it is possible to have arguments in the constructor that can't be fulfilled by the container. Is this possible with the Microsoft.Extensions.DependencyInjection libraries and IServiceProvider? If not, what is a clean solution for this sort of problem?

For example:

class InContainer
{
    public InContainer(NotInContainer dependency) { ... }
}

class Consumer
{
    public Consumer(IServiceProvider serviceProvider)
    {
         NotInContainer currentDependency = ... // from some other source
         // passing the anonymous object here is not supported, 
         // but I would like to 
         InContainer = serviceProvider.GetService<InContainer>(
             new { dependency = currentDependency }
         );
    }
}
1
  • Do you mean during registration, or on a per instance basis? Commented Nov 3, 2017 at 13:03

3 Answers 3

5

In your example, you provide the serviceProvider with a runtime value currentDependency. Application components should not require runtime data during construction, as explained here. The solution is to refactor your design, as explained in that article.

About optional arguments:

The fact that some DI Containers support optional arguments, doesn't make it a good practice to use them. As a matter of fact, injection constructor arguments should never be optional.

As explained in this article:

An optional dependency implies that the reference to the dependency will be null when it’s not supplied. Null references complicate code because they require specific logic for the null-case. Instead of passing in a null reference, the caller could insert an implementation with no behavior, i.e. an implementation of the Null Object Pattern.

If not, what is a clean solution for this sort of problem?

As stated, the Null Object pattern is the solution for this, even when using a DI Container that actually supports optional constructor dependencies.

Sign up to request clarification or add additional context in comments.

6 Comments

But OP dependency is not optional. It is just not present in container and he wants to provide it at resolve time.
Yes that makes sense, but still first part of your answer seems not relevant to the question.
@Evk: well.... according to the title, the question is about "optional constructor arguments" :)
This doesn’t seem like it would work well for my situation
You have the point :) Though I usually ignore titles since they very often are different from what is being asked. In case of your answer one might not even reach last paragraph, think that first part is not relevant for their sutation and downvote.
|
3

Normally, I create a factory by hand in this case.

public class TheFactory
{
    public TheFactory( SomeType fromContainer )
    {
        _fromContainer = fromContainer;
    }

    public IProduct Create( SomeOtherType notFromContainer ) => new TheProduct( _fromContainer, notFromContainer );

    private readonly SomeType _fromContainer;

    private class TheProduct : IProduct
    {
        // ...
    }
}

If you need per-product dependencies from the container, the factory's Create has to resolve them. Or, in the case of e.g. unity, the factory gets a Func from the container.

2 Comments

I suppose then the factory could track any ‘IDisposable’s and clean them up.
It probably doesn't even need to, given you treat a call to Create the same way as new - it creates an instance that belongs to the caller.
1

Is there a reason not to just do this? I had a situation where I needed the HttpContext, but in the course of initializing my web app, a few calls would inevitably be made. Since it was during init, there was no request and hence no context.

This was all for fairly typical database auditing, by the way, so obviously you want the username for changes the user makes, but during init it just ends up using "SYSTEM" or something like that. So...

public class CurrentHttpUserProvider : ICurrentUserProvider
{
    public CurrentHttpUserProvider(IServiceProvider container)
    {
        var httpContext = container.GetService(typeof(HttpContext)); // just returns null if it can't be found...
        if (httpContext != null)
            CurrentUser = ((HttpContext)httpContext).User.Identity.Name;
        else
            CurrentUser = "SYSTEM";
    }

    public string CurrentUser { get; set; }
}

This seems to be like it would be 100% as efficient as if I'd just injected the HttpContext as a constructor param, but perhaps I'm wrong.

3 Comments

You're using the Service Locator anti-pattern. That's one reason one might not use it.
I know this is old, but there is an IHttpContextAccessor that can be used for this purpose. It contains a property for the HttpContext that can be null.
@Dave You're 100% right. This is very old. The correct way to do it these days is with IHttpContextAccessor. I either didn't know about it back then or for some reason it didn't work for me then.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.