JasperFx / lamar

Fast Inversion of Control Tool and Successor to StructureMap
https://jasperfx.github.io/lamar
MIT License
563 stars 118 forks source link

What is the difference between AddLamar() and UseLamar()? #251

Closed hellfirehd closed 3 years ago

hellfirehd commented 4 years ago

I expected both of these tests to pass but the first one doesn't. What am I missing?

[Fact]
public void ServiceCollection_AddLamar()
{
    var services = new ServiceCollection();
    services.AddLamar();
    var provider = services.BuildServiceProvider();

    using (var scope = provider.CreateScope())
    {
        Assert.IsType<Container>(scope);
    }
}

[Fact]
public void HostBuilder_UseLamar()
{
    var hostBuilder = new HostBuilder();
    hostBuilder.UseLamar();
    var host = hostBuilder.Build();

    using (var scope = host.Services.CreateScope())
    {
        Assert.IsType<Container>(scope);
    }
}

I can live with using HostBuilder but just don't understand why the difference.

FinalBreaker commented 4 years ago

BuildServiceProvider() in the first test always creates a Microsoft.Extensions.DependencyInjection.ServiceProvider. AddLamar may add all the necessary classes to use it but BuildServiceProvider() build the MSFT version

fundamentally, I wouldn't consider this a Lamar issue

RyanThomas73 commented 4 years ago

Lamar should provide a means to use the BuildServiceProvider() extension method on a service collection that is an instance of ServiceRegistry or is otherwise constructed from it. Right now this doesn't seem possible as the ServiceRegistry will only contain the AssemblyScanner 'placeholders' and won't actually apply the scanning. A cursory look through the code suggests that ScanningExploder and ServiceGraph handle this but ScanningExploder is an internal only type and ServiceGraph expects a root scope (e.g. container) to construct.

A way to explicitly expand the registry and get the IServiceCollection result would be ideal.

public class BasicRegistry : ServiceRegistry
{
    public BasicRegistry()
    {
        Scan(_ => {
             // apply scannings
         });

        // Custom registrations
    }
}

public class Example
{
    public void DoSomething() 
    {
         var serviceCollection = new BasicRegistry().Expand();
         using var serviceProvider = serviceCollection.BuildServiceProvider();

         // ... etc ...
    }
}
jeremydmiller commented 3 years ago

@RyanThomas73 Just pass ServiceCollection into the constructor of a Lamar Container which also implements the .Net Core IServiceProvider interface. I'm not seeing any real reason why you'd need a BuildServiceProvider() hook here.

RyanThomas73 commented 3 years ago

@jeremydmiller The point of being able to use the the BuildServiceProvider() is that the requirement of passing it into a lamar container constructor is misleading from an isolation / decoupling standpoint. Lamar's service registry implements IServiceCollection but this is misleading, as the implementation can only be correctly used when combined with the lamar container.

IServiceCollection serviceCollection = SharedLibraryOrHelperToSetupDefaultServiceCollection();

// Working with an IServiceCollection here I would expect it to work as an IServiceCollection without 
// requiring the consumer of SharedLibraryOrHelperToSetupDefaultServiceCollection() to know or care
// that this service collection is implemented using Lamar and requires them to consume is a special way for it to work.
var serviceProvider = serviceCollection.BuildServiceProvider();
jeremydmiller commented 3 years ago

@RyanThomas73 You're treading into "I take pull requests" territory here. I'm happy to take a PR if you want this, but nobody else is asking for this feature.