Here's a good starting point:
https://github.com/simpleinjector/SimpleInjector/issues/41#issuecomment-111438146
We should capture all of these in our specification tests.
/cc @lodejard @Eilon @pranavkm
That would be nice! :+1: Microsoft.Extensions.DependencyInjection.Specification.Tests
is already a good starting point, but I still think there might be "undocumented" expected behaviors.
May be not a right place, but it is quite explicit and exemplary how DryIoc setups DI adapter:
if (container.ScopeContext != null)
throw new ArgumentException(
"Adapted container uses ambient scope context which is not supported by AspNetCore DI.");
var adapter = container.With(rules => rules
.With(FactoryMethod.ConstructorWithResolvableArguments)
.WithFactorySelector(Rules.SelectLastRegisteredFactory())
.WithTrackingDisposableTransients()
.WithImplicitRootOpenScope());
@dadhi it's definitely the right place :smile:
Moving to backlog.
On the Autofac implementation we're definitely getting questions (as outlined in that SimpleInjector issue) about whether there's a guaranteed order of things coming back from enumerable/collection services. Here's the latest one.
Is there a prescribed (or required) order for enumerables returned from the service provider?
Out of curiosity, what parts of the ASP.NET Core framework explicitly depend on the DI container and registration system?
I know that the WebHostBuilder
is responsible for creating the registration collection and provides services like IApplicationLifetime
and IHostingEnvironment
as injectable. But environment configuration is _context_ and could be provided within the existing context objects (IApplicationBuilder
and HttpContext
).
MVC registers services. But does it need to? Does it still have factory classes for constructing controllers and views? It seems odd to me that to use a piece of middleware I need to remember to register it's services in a different location.
Having a single DI container does seem to provide a way to implicitly make middleware "DI friendly", but this too could be solved by providing a middleware factory and allowing middleware to be constructed via that, again bypassing the conforming container. In fact, Autofac provided this kind of middleware factory for OWIN.
@halter73 can you respond to some of these questions?
AFAIK, IConfigureOptions
\ OptionsManager
is the only collection that we use as IEnumerable<T>
but doesn't have any explicit ordering to it. Might be useful to figure out if we rely on the setups to be run in a specific order.
Even with IConfigureOptions
we've found people expect them to run in order even if that's not specified.
Yes - enumerables should be returned in the order added - so fwk services should appear before app servces in an enumerable because the fwk services are what is setting the default.
So the expectation is correct. There was a set of base test cases in the DI repo that could be run against the built-in container, and could be used by third-party container adapters to validate correctness of the assumptions. Was the project with the abstract base test cases deleted at some point? It would have been the place where the assumptions were codified.
The tests are still there but there is no test around the ordering of enumerables, which is where some of the challenge is arising.
IStartupFilter
instances need to be ordered. Otherwise the internally registered filter that registers the per-request-scope middleware winds up out of order (and potentially last instead of first).
Actually, I have another issue with this: because the framework is registering services, how do I register services before it? Say I want to replace the per-request-scope generating filter. It is written to do nothing if a scope has already been set, but I can't register a filter before it, so it will _always_ be the first middleware run.
Regarding the small set of specifications brought up in https://github.com/simpleinjector/SimpleInjector/issues/41#issuecomment-111438146. Most of these already have specification tests:
Off the top of the head, here are some other things that we do that we probably don鈥檛 test:
@davidfowl Do any of these warrant adding a specification test for?
Registrations for collections are handled by appending registrations for the service type one after the other to the registrations list.
This refers to the fact that some DI containers cannot adapt the IServiceCollection
s list of services into correct registrations. Some containers do not make assumptions about the cardinality of service registrations and demand explicit declaration of cardinality. You haven't just defined a specification for a service container, but also for a service registration system.
Even with all of these "Conforming container" specifications there is still a big impedance mismatch between your container's (and service registration system's) features and the features available on the DI container of MY choice.
The more I look at this problem the more I wonder what exactly is being solved by having IServiceProvider
available at all. From a library design point. we don't need it, we just need to implement classes following core DI principals and favouring constructor injection. From a framework design point it forces other frameworks to conform to your container and limit themselves. From an application point of view it encourages Service Locator.
@PleasantD Given multiple registrations for a given service type in an IServiceCollection
, containers are free to register those services with an explicit declarations of cardinality.
I personally don't know whether asking containers to maintain ordering is a bridge too far, but I do know that ordering is a "feature" that we have come to rely on in several places in ASP.NET. Initially, we planned to fix our usage not to rely on ordering (and I wish we had/could), but there's a risk that we wouldn't catch everything prior to 1.0.
As for exposing IServiceProviders
in the HttpContext
and in other places: something needs to resolve the first service. It can't be constructor injection all the way down. If people want to use the service locator pattern, they'll find a way.
@smitpatel Said he could help investigate whether Autofac works as expected with EF. @ajcvickers told me EF should _not_ rely on service ordering.
Edit: added _not_
replacing
var serviceProvider = services.BuildServiceProvider());
with
var builder = new ContainerBuilder();
builder.Populate(services);
var container = builder.Build();
var serviceProvider = container.Resolve<IServiceProvider>();
works as expected in EF functional tests.
While you're extending the Spec Tests, are you sure that the following code snippet from DependencyInjectionSpecificationTests.cs is correct?
public static TheoryData ServiceContainerPicksConstructorWithLongestMatchesData
{
var fakeService = new FakeService();
var multipleService = new FakeService();
var factoryService = new TransientFactoryService();
var scopedService = new FakeService();
[...]
new ServiceCollection()
.AddSingleton<IFakeService>(fakeService)
.AddSingleton<IFakeMultipleService>(multipleService)
.AddSingleton<IFakeScopedService>(scopedService)
.AddSingleton<IFactoryService>(factoryService),
new TypeWithSupersetConstructors(multipleService, factoryService, fakeService, scopedService)
}
according to the IFake* names the fourth parameter is a scopedService, but all 4 services are added as singletons. Either the name is wrong or the AddSingleton is wrong. According to the names I would have also expected other implementations.
@Tornhoof seems like IFakeScopedService
is a misnomer in this case.
In my Kestrel issue (ObjectDisposedException
during shutdown) I noticed that Autofac as DI container doesn't provide an IServiceProvider
that implements IDisposable
which Hosting would call during shutdown.
I have replaced return container.Resolve<IServiceProvider>();
from the example in the docs with the following code:
// var container = containerBuilder.Build();
var result = new disposingServiceProvider();
result.ServiceProvider = container.Resolve<IServiceProvider>();
result.Container = container;
return result;
with
private class disposingServiceProvider : IServiceProvider, IDisposable
{
public IServiceProvider ServiceProvider;
public IContainer Container;
public object GetService(Type serviceType)
{
return ServiceProvider.GetService(serviceType);
}
public void Dispose()
{
Container.Dispose();
}
}
And this seems to fix my shutdown issues.
Need to add a spec test for the service provider being disposable to capture that behavior.
@davidfowl let's discuss this when you're around
We are closing this issue because no further action is planned for this issue. If you still have any issues or questions, please log a new issue with any additional details that you have.
Most helpful comment
In my Kestrel issue (
ObjectDisposedException
during shutdown) I noticed that Autofac as DI container doesn't provide anIServiceProvider
that implementsIDisposable
which Hosting would call during shutdown.I have replaced
return container.Resolve<IServiceProvider>();
from the example in the docs with the following code:with
And this seems to fix my shutdown issues.