Aspnetcore: Add support for child containers

Created on 1 Jan 2018  Â·  21Comments  Â·  Source: dotnet/aspnetcore

_From @glen-84 on Saturday, June 6, 2015 12:58:40 PM_

Use case: You add a "module" (separate assembly) to your web application which contains controllers, views, etc. The MVC parts make use of an IStringLocalizer implementation that loads strings from a JSON file (for example). When you "register" the module, it adds its dependencies to the DI container of the host application, including the string localizer implementation. The host application then sets its own implementation for IStringLocalizer, which might load strings from a database, overwriting the previous implementation. Now the module's controllers and views will be using the wrong implementation of the string localizer, and the module's message strings will not be loaded.

If each module (or assembly or logical set of controllers/views) had its own child container, the instances would be isolated, and each module could have its own set of implementations, which would first be resolved from the child container, and then fall back to the parent container when not found.

This is just one example, and I'm sure there are many more.

/cc @brockallen @mdekrey (from https://github.com/aspnet/Announcements/issues/28)

_Copied from original issue: aspnet/DependencyInjection#246_

Most helpful comment

To question the need for nestable containers leads in consequence to question component based development. Almost every relevant application beyond "Hello world" rather sooner than later needs to be segregated and therefore even for in-house development to be split in components.
Composing components of course includes their self-initialization instead of spreading internals around, which the internal dependencies are. Changing anything in a component that leads to adaptions of DI has to be maintained in every consuming component or application just because the lack of this feature. That breaks self-responsibility, OCP, …
Based on experience and considering the afore mentioned I tend to see the ability to nest containers as feature _completeness_ not richness. I'd rate the gain by supporting or even promoting good coding styles way higher than the (feared?) effort to add the feature.
It is quite disappointing to see that ASP.NET Core provides a standard DI, which therefore becomes the first choice, but by denying the request for the container nesting denies support for proven, hence desirable practices. To point to competitors products implies to rip off all occurrences of ServiceCollection and re-write all bootstrap code instead of just using a feature. Am I the only one considering this poor?
Please re-consider the case and add container nesting. It can't be that hard to accomplish!

All 21 comments

_From @davidfowl on Saturday, June 6, 2015 4:42:20 PM_

Use case: You add a "module" (separate assembly) to your web application which contains controllers, views, etc.

Sounds like something that can be fully implemented in a stand alone way and doesn't have to be built in.

We're not trying to be a feature rich DI container so feature like these and other named providers won't be implemented. If you want features like child containers, use a DI container that supports them.

If each module (or assembly or logical set of controllers/views) had its own child container, the instances would be isolated, and each module could have its own set of implementations, which would first be resolved from the child container, and then fall back to the parent container when not found.

There is a single container within the application. If you want support for something like this, it can be built on top of what we provide (make your own service collection and pass that built service provider around).

Orchard does something like this already with Autofac. I'd look there for inspiration.

_From @brockallen on Saturday, June 6, 2015 5:27:53 PM_

Why isn't the ActionContext in MVC done this way?

IOW, can we use this approach you suggest to do injection of a specific per-request instance of a type?

I guess, what's really being requested is something like IHttpContextAccessor.

_From @davidfowl on Saturday, June 6, 2015 8:05:02 PM_

File a bug on MVC then? Currently MVC registers IScopedInstance<T> to achieve similar things to IHttpContextAccessor (though they are pretty different).

_From @mdekrey on Sunday, June 7, 2015 11:49:33 AM_

The problem with implementing it in a stand alone way is for package authors, rather than end-product-developers. As @atrauzzi mentioned in the Announcements thread (wrong spot for us to jump on that before, @davidfowl - sorry for that), it would be atrocious for a package author to require a specific DI framework; we're hoping for hooks that can be tied into by all DI frameworks.

mdekrey/DependencyInjection@04f92ae19311478254f97808297f1006ba747e2d adds Child Container capabilities to the default DI container in a non-breaking way and starts the progress for Ninject and Autofac, though I am not familiar enough with them to complete the job. I'd be happy to learn it and complete the work if that would mean a pull request would be accepted, though.

_From @davidfowl on Sunday, June 7, 2015 11:42:39 PM_

The problem with implementing it in a stand alone way is for package authors, rather than end-product-developers. As @atrauzzi mentioned in the Announcements thread (wrong spot for us to jump on that before, @davidfowl - sorry for that), it would be atrocious for a package author to require a specific DI framework; we're hoping for hooks that can be tied into by all DI frameworks.

That's a grand claim. This is a brand new DI abstraction and implementation that is only used in ASP.NET as it stands.

If you're thinking about adding this generic capability to the DI abstraction then I'd suggesting doing some more research to see if the most popular DI containers support this in a first class way. ASP.NET 5 doesn't have a way to configure the child container, so even after adding this support to the DI system, ASP.NET 5 won't take advantage of it (btw I'm not a fan of the default parameter, I'd rather it be added in a first class way if we chose to do it). I'm also not a fan of this being on by default. I think the implementation we have today is better and child containers should be something additional but not turned on by default.

_From @mdekrey on Monday, June 8, 2015 5:45:45 AM_

I'm not sure what the grand claim is - I'm fairly certain that if it's part of the framework that Microsoft puts out, it'll become a standard that everyone implements. (ProviderBase sure was.) And I'm REALLY excited that DI is something that is used by the framework by default.

I agree regarding the default parameter - it shouldn't be there; it was to prevent breakage from anyone else who is currently using it, such as MVC 6; even in WIP projects I have the preference to not introduce breaking changes.

Both Autofac and Unity allow adding registrations to child lifetime scopes; Ninject does this through an officially maintained extension. The MVC extensions for both Autofac and Unity have object registrations under the request's scope, and Autofac even registers the HttpContextBase for you.

Unfortunately, I don't think we can "turn on" child containers without at least some additional support for other hooks in the framework. I'd rather see high-level feature requests (such as this one) than low-level ones, myself, but that's just one guy's opinion. (I believe low-level features would be harder to implement in every DI container; this one would need custom scopes and registrations after initial configuration. Ninject does not support the latter as far as I know without making a child scope.)

Again, I want to say, I'm really, _really_ excited that DI is in the Microsoft.Framework. I'm just hoping that it chooses to provide a more featureful experience.

_From @davidfowl on Monday, June 8, 2015 12:02:27 PM_

it would be atrocious for a package author to require a specific DI framework; we're hoping for hooks that can be tied into by all DI frameworks.

That claim. The only real integration point for this that would cause package authors to depend on a specific DI framework would be if they wanted to expose the configuration of the nested container. If a library author just depended on DI features, then configuration the child container is about the "system" creating the child container itself. In the case of an http request, that would be the request itself.

Unfortunately, I don't think we can "turn on" child containers without at least some additional support for other hooks in the framework.

All of the hooks exist already. But we wouldn't design anything around this today. It's a bigger question if we decide that we need to design features around child containers (like exposing ConfigureRequestContainer etc) to which I think the answer is no.

I believe low-level features would be harder to implement in every DI container; this one would need custom scopes and registrations after initial configuration. Ninject does not support the latter as far as I know without making a child scope.

I think the next step in making child containers real would be to flesh out what other DI containers support to see where it would fall over. As an example, we used to have a fallback mechanism that allowed containers to chain GetService calls. That ended up being a disaster to implement in order DI systems and the feature was dropped as a result.

_From @brockallen on Monday, June 8, 2015 1:30:10 PM_

So for child containers now, I suspect it can be implemented using IHttpContextAccessor as the model.

You'd need to design IFooAccessor whose implementation depended on the IHttpContextAccessor to use the HttpContext's Items as the place to read/write the Foo. Somewhere in your pipeline you know to set the right entry in the HttpContext's Items collection, and the anything that needs Foo injects the IFooAccessor.

Sound about right? Or maybe there's a more obvious or easier way I'm missing?

_From @matthewDDennis on Wednesday, June 17, 2015 6:55:19 PM_

Implementing a child container in the existing framework is not difficult. As a quick proof of concept:

``` c#
using System;

namespace Microsoft.Framework.DependencyInjection
{
public class ChildServiceProvider : IServiceProvider
{
private readonly IServiceProvider _child;
private readonly IServiceProvider _parent;

    public ChildServiceProvider(IServiceProvider parent, IServiceProvider child)
    {
        _parent = parent;
        _child = child;
    }

    public ChildServiceProvider(IServiceProvider parent, IServiceCollection services)
    {
        _parent = parent;
        _child = services.BuildServiceProvider();
    }

    public object GetService(Type serviceType)
    {
        return _child.GetService(serviceType) ?? _parent.GetService(serviceType);
    }
}

}

To create the child service provider:

``` c#
namespace ChildContainer
{
    public interface IMyType1 { }
    public interface IMyType2 { }

    public class MyType1 : IMyType1 { }
    public class MyType2 : IMyType2 { }

    public class Sample
    {
        IServiceProvider CreateChildServiceProvider(IServiceProvider parent)
        {
            var services = new ServiceCollection();

            services.AddTransient<IMyType1, MyType1>()
                    .AddSingleton<IMyType2, MyType2>();

            return new ChildServiceProvider(parent, services);
        }
    }
}

How to use and set the IServiceProvider in use to the child container is a different issue,

_From @mdekrey on Thursday, June 18, 2015 3:59:36 AM_

It's unfortunately not that easy, @matthewDDennis, due to the way IServiceProvider handles IEnumerables and other Open service resolutions. Thanks for trying to help, though!

_From @matthewDDennis on Thursday, June 18, 2015 4:57:29 AM_

Why wouldn't it work?
IServiceProvider doesn't _handle_ 'IEnumerables etc. That is done in the ServiceProvider implementation.
Since my ChildContainer, using the ServiceCollection.BuildServiceProvider, is just creating a ServiceProvider and using it, which already handles everything you are concerned about.

Since it is an IServiceProvider, all the extension methods would work as well.

_From @matthewDDennis on Thursday, June 18, 2015 5:13:46 AM_

I just realized that ServiceProvider implements IDisposable, so I've added support for disposable IServiceProvider implementations.

``` c#
using System;

namespace Microsoft.Framework.DependencyInjection
{
public class ChildServiceProvider : IServiceProvider, IDisposable
{
private readonly IServiceProvider _child;
private readonly IServiceProvider _parent;

    public ChildServiceProvider(IServiceProvider parent, IServiceProvider child)
    {
        _parent = parent;
        _child = child;
    }

    public ChildServiceProvider(IServiceProvider parent, IServiceCollection services)
    {
        _parent = parent;
        _child = services.BuildServiceProvider();
    }

    public void Dispose()
    {
        (_child as IDisposable)?.Dispose();
    }

    public object GetService(Type serviceType)
    {
        return _child.GetService(serviceType) ?? _parent.GetService(serviceType);
    }
}

}
```

_From @adamhathcock on Tuesday, October 6, 2015 3:56:27 AM_

I've been attempting to use this code. You cannot use ChildServiceProvider along side IServiceScopeFactory. I've extended the ChildServiceProvider implementation to have a ChildServiceScopeFactory, etc which works but I have a feeling it's going to fall down at some point or require more reimplementation.

I'd rather have the CreateScope method take an overload of an IServiceCollection that are descriptors just for the child context. The reasoning is that I have an object that applies only the the child scope and doing AddInstance to the parent's service collection at creation would obviously share across all scopes.

I looked at just doing this with a simple PR but the problem is that when I started digging I'd have to change more of ServiceProvider to take not only a parent ServiceProvider but a ServiceCollection and merge the descriptors. Didn't want to think that through at the moment :)

Is this way off base?

_From @mdekrey on Thursday, October 8, 2015 5:51:35 AM_

@adamhathcock :+1: - the quick implementation above really falls apart upon close inspection, especially with the special handling of IEnumerable<T>. (It pulls from either the child or the parent, not combining the two as expected by child containers.) I had gotten pretty close, but could not get it to behave in a way that was SOLID, nor performant, since the resolution table was completely internal. I ultimately gave up and created more objects like the IHttpContextAccessor, which was not elegant, but did work for my problem.

_From @TFleury on Sunday, October 2, 2016 2:23:41 AM_

Multitenancy is a case where child containers is needed.
SaasKit project searched for a way to implement a per-tenant container, but ended up using StructureMap. @benfoster wrote an interesting blogpost on this.

Orchard project achieved this cloning the parent service collection and injecting instances of Singleton services (code here). It seems to work but it resolves all Singleton services (even if not needed), and there is a known issue with Singleton generics.

On DI Notes wiki page, there is a reference to "chaining containers". Are there plans to implement this ?

_From @davidfowl on Sunday, October 2, 2016 9:44:17 AM_

Nope.

_From @pksorensen on Friday, March 17, 2017 4:06:48 PM_

The proof of concept given here has some issues.

IEnumerable : When a class like OptionsManager needs IEnumerable, then it only get those from the child container.

Same when DI instanciate a new type, it dont call GetService for each of its dependencies, as it tries to be smart about how it activates its constructor params - and it again might miss some registrations from the parent registrations.

Any news on this issue or a better way (that works) to work-around this limitation?

As @pksorensen says, when a service gets resolved in the child IServiceCollection, it tries to resolve all dependencies of that service from that child container and no longer considers the parent container.

We have no plans to do this. Use autofac if you need child containers.

To question the need for nestable containers leads in consequence to question component based development. Almost every relevant application beyond "Hello world" rather sooner than later needs to be segregated and therefore even for in-house development to be split in components.
Composing components of course includes their self-initialization instead of spreading internals around, which the internal dependencies are. Changing anything in a component that leads to adaptions of DI has to be maintained in every consuming component or application just because the lack of this feature. That breaks self-responsibility, OCP, …
Based on experience and considering the afore mentioned I tend to see the ability to nest containers as feature _completeness_ not richness. I'd rate the gain by supporting or even promoting good coding styles way higher than the (feared?) effort to add the feature.
It is quite disappointing to see that ASP.NET Core provides a standard DI, which therefore becomes the first choice, but by denying the request for the container nesting denies support for proven, hence desirable practices. To point to competitors products implies to rip off all occurrences of ServiceCollection and re-write all bootstrap code instead of just using a feature. Am I the only one considering this poor?
Please re-consider the case and add container nesting. It can't be that hard to accomplish!

@ApolloCreek I tried to add this functionality with the idea to create a PR, but I'm afraid it is not that simple as a large part would have to be rewritten/refactored to make this possible (at least as far as I can see/understand).

Was this page helpful?
0 / 5 - 0 ratings