-
Notifications
You must be signed in to change notification settings - Fork 317
Proposal: Add IServiceProviderFactory #442
Comments
I like it! Reminds me a bit of some of the things I did with the Nancy bootstrapper prototype. This would cut down on the amount of code someone would have to write when you just want to use a third party container, but don't care about its registration APIs. You could question the need for a third party container in this case, though... But it looks like a nice addition. |
I like it, particularly in combination with the ConfigureServices overload. Getting a ContainerBuilder (or whatever your container takes) right into the method would save a lot of boilerplate (stuff I actually have in a few projects already). |
Yea, that's why paired with the It would be great to get some more feedback here to see if this is a viable approach. We plan to use this ourselves to test out our system with different DI containers. |
I wonder if this should just be a hosting specific interface. To hang extension methods off the |
Makes sense to split out a separate package, then. Something like
Do I smell an extension to |
Does it mean, that implementation of provider is supposed to be based on "normal" adapter with it |
I like it a lot as well, especially being able to provide container specific extension that allows for configuration. @davidfowl what do you see as the time frame for us to create providers? Should we start planning to create packages soon or a couple months down the road (I see 1.1.0 but not sure how far off that is)? |
It's the container after the populate call.
@DamianEdwards roadmap for 1.1? |
I've commited the DI side of things. Will push the Hosting changes tomorrow. |
It occurs to me this new interface can also be used to dispose the container and solve issues like this #379 (comment). Any thoughts on this? The new interface would look like this: public interface IServiceProviderFactory<TContainerBuilder>
{
TContainerBuilder CreateContainerBuilder(IServiceCollection services);
IServiceProvider CreateServiceProvider(TContainerBuilder containerBuilder);
void Dispose(IServiceProvider serviceProvider);
} This assumes you can get the disposable thing from the Alternatively (I'm not a fan of this one). public interface IServiceProviderFactory<TContainerBuilder, TContainer>
{
TContainerBuilder CreateContainerBuilder(IServiceCollection services);
TContainer BuildContainer(TContainerBuilder containerBuilder);
IServiceProvider CreateServiceProvider(TContainer containerBuilder);
void Dispose(TContainer container);
} |
With option 1 would the idea be that in the Dispose method we cast the IServiceProvider to something that exposes the TContainer that we then dispose or something a little more meta where we call the service provider to get the TContainer to then dispose? Either way I think I like option 1 more, it's a little less code and a little less specific on how the Factory is implemented, option 2 requires you to expose TContainerBuilder & TContainer as well as having a BuildContainer method (who's to say every factory needs to implement a BuildContainer method) my $.02 |
I see the draw to the first one since it's a little simpler and more generic, but it occurs to me you'll end up with boilerplate every time: public void Dispose(IServiceProvider serviceProvider)
{
var castProvider = serviceProvider as MyContainerServiceProvider;
if(castProvider == null)
{
return;
}
castProvider.Container.Dispose();
} Seems like that could be avoided with the second interface. |
If you're forcing an implementation of the public interface IServiceProviderFactory<TContainerBuilder, TProvider>
where TContainer : IServiceProvider, IDisposable
{
TContainerBuilder CreateContainerBuilder(IServiceCollection services);
TContainer CreateServiceProvider(TContainerBuilder containerBuilder);
} This way, the hosting layer could just call the Example: public class ExampleAutofacServiceProviderFactory : IServiceProviderFactory<ContainerBuilder, AutofacServiceProvider>
{
public ContainerBuilder CreateContainerBuilder(IServiceCollection services)
{
var builder = new ContainerBuilder();
builder.Populate(services);
return builder;
}
public AutofacServiceProvider CreateServiceProvider(ContainerBuilder containerBuilder)
{
return new AutofacServiceProvider(containerBuilder.Build());
}
private class AutofacServiceProvider : IServiceProvider, ISupportRequiredService, IDisposable
{
public AutofacServiceProvider(ILifetimeScope lifetime)
{
Lifetime = lifetime;
}
private ILifetimeScope Lifetime { get; }
public object GetRequiredService(Type serviceType) => Lifetime.Resolve(serviceType);
public object GetService(Type serviceType) => Lifetime.ResolveOptional(serviceType);
public void Dispose() => Lifetime.Dispose();
}
} Now, getting to the provider factory is much harder with two generic arguments though 😢 Also, why can't the hosting layer just do what @tillig showed? The classic |
Yep, that's the only kicker. You need to be able to get it back somehow.
Funny, today I was talking to @halter73 about using generic constraints.
It does that today but there was some push back on the container implementing
Yep, that's another downside. Should we just keep down casting the |
Maybe I'm old school but I kinda like the explicitness of Dispose being called on IServiceProviderFactory. The factory creates the contianer, the factory disposes the container (there is a synergy to it). @davidfowl casting to a concrete implementation in the dispose of the factory seems reasonable to me. |
@ipjohnson Sounds good. It makes sense for the factory to dispose the thing it created. As long as the provider can round trip the right information I think it's a better model as well. Here's a sample of the current bits you can all play with: https://github.com/davidfowl/ServiceProviderFactorySample |
@davidfowl very cool, what would you think of containers offering overloads for the UseXXXX(configuration) method. Example:
Would this allow you to swap the service provider and do service configuration without doing a startup? |
I'd prefer to keep the configuration of services in the application itself. The application really starts in the Startup class. The WebHostBuilder code should be considered the "host" |
fair enough, what about configuration that might be passed in the container constructor? Or for that should we skip the factory and construct the container in the startup like it's done now? |
You could pass those to the factory ctor or inject them as a service. Do you have an example of what configuring the container would look like? We want to deprecate the pattern where the application returns the |
For example in Grace you have the option of passing in a IKernelConfiguration object. In DryIoc you can pass in a Rule object. Consider them options that need to be provided before the container is constructed. For the most part it's going to be application specific. But I could see passing in a configuration that's host specific (us IL generation for .Net 4.6.2 but use Linq expressions on other platforms). |
@ipjohnson it might be worth forking the samples and adding support for different IOC adapters so we can flesh out any problems. |
@davidfowl I'll give it a whirl this weekend. I'll pose this question to the group, in the example shown for autofac how would allow the developer to set the optional ContainerBuildOptions parameter for IContainer.Builder |
@davidfowl @ipjohnson The interesting bit in DryIoc that adapter actually returns new container. So I am bit struggling how to configure services with DryIoc with less possible boilerplate. For now I may provide something like this:
|
@ipjohnson This is how I did it with StructureMap: davidfowl/ServiceProviderFactorySample@46fba0f. Just pass the options to the factory, which again passes it to the builder/container when it's created. |
@dadhi not a fan. It's actually not using any of the new interface. What's the actual problem? Again, we're trying to move away from returning the IServiceProvider from the Configure method. |
@davidfowl Ok, then using the new
Looks fine. |
@khellang I like that, I think I must have been tired last night because I missed david's comment that passing into the factory constructor was a valid option. I'm sold. |
How is this used in the current Asp.NetCore MVC stack ? I cannot find any reference to it anywhere |
@slaneyrw It's used below MVC in the stack. Check out aspnet/Hosting 😄 |
Ok, got this run using a Unity builder but having problems because of missing type mappings in the default container setup. Looks like the default behaviour of DI system is to inject null when an interface is not mapped. Most containers will fail to inject on missing. Some will build a default implementation - which is worse. I found 3 issues like this
I used the Using optional constructor arguments is not a great DI practise, IMHO. |
We had to make asjustments in ctor selection for StructureMap as well. The adapter expects the container to pick the greediest ctor it can satisfy, not just the greediest. |
@slaneyrw Our DI system does not inject null when an interface is not mapped. I think you're running into the issue mentioned by @khellang where our DI system "expects the container to pick the greediest ctor it can satisfy, not just the greediest." This is certainly the case for MvcDataAnnotationsMvcOptionsSetup and IStringLocalizerFactory. |
@halter73 Thanks for the confirmation. This can lead to some worrying behaviour and is an interesting design choice |
I added this to my servicecollection:
in a At what place should I register my factory implementation for it being picked up? |
@pksorensen Is If so, you likely need to implement a custom IServiceScopeFactory that gets returned from |
I came to the conclussion that since I am setting up the application directly on WebHostBuilder without a startup class, that IServiceProviderFactory is not used in 1.1.1. When i used latest dev CI nugets i got things working. |
@pksorensen Make sure you change your startup class to inherit from I'm using ASP.NET Core 1.1.1 and it definitely uses the provider factory |
This will allow 3rd party containers to plug into the default pipeline when the application doesn't:
See aspnet/Hosting#829 for consumption
/cc @tillig @khellang
The text was updated successfully, but these errors were encountered: