-
Notifications
You must be signed in to change notification settings - Fork 313
Add support for child containers #246
Comments
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.
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. |
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. |
File a bug on MVC then? Currently MVC registers |
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. |
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. |
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. ( 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 |
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.
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 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. |
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? |
Implementing a child container in the existing framework is not difficult. As a quick proof of concept: 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: 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 |
It's unfortunately not that easy, @matthewDDennis, due to the way |
Why wouldn't it work? Since it is an |
I just realized that 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);
}
}
} |
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? |
@adamhathcock 👍 - the quick implementation above really falls apart upon close inspection, especially with the special handling of |
Multitenancy is a case where child containers is needed. 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 ? |
Nope. |
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. |
This issue was moved to dotnet/aspnetcore#2355 |
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 forIStringLocalizer
, 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 aspnet/Announcements#28)
The text was updated successfully, but these errors were encountered: