DevTeam / Pure.DI

Pure DI for .NET without frameworks!

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

IsRegistered(Type type) check

Serg046 opened this issue · comments

The current asp.net integration seems not to give compile-time check advantage over the default asp container because it works based on OnCannotResolve hint which disables compile-time checks. I'd use another approach where you combine pure and asp containers instead of integration:

class CustomServiceProvider : IServiceProvider 
{
    private readonly Composition _composition;
    private readonly IServiceProvider _aspContainer;

    public object? GetService(Type type)
    {
        return _composition.HasRegistrationFor(type) ? _composition.Resolve(type) : _aspContainer.GetService();
    }
}

Then the pure container could be without any hints or extensions preventing from compile checks. You just need to take care of proper dependencies' disposal. However, I don't see a way to do that without catching an exception which is super slow. I'd suggest extending Pure.DI with such a possibility. It might be a registration check or Resolve method that doesn't throw implemented by a new hint or so.

Please think of this suggestion. In case you like/okay with the proposal, I can try to contribute if you give implementation details (method/hint names, etc).

Yes, you are absolutely right. This is because at compile time the generator has no idea about the set of services that will be provided by ServiceCollection, since this information is available only at runtime. In this case, checks for the possibility of dependency injection are disabled at compile time, otherwise there is no way.

I would of course be very happy if you manage to solve this problem. But I have no idea how this is possible, and would appreciate your contribution and be willing to answer your questions.

Maybe you mean return null instead of throwing an exception at runtime?

Yes, I meant retuning null by "Resolve method that doesn't throw", like asp container does. But bool IsRegistered(Type serviceType) might be a better choice. Guess this functionality might be useful for other cases as well as for mine.

As for "services that will be provided by ServiceCollection", you can always "Arg" or "RootArg" them like this:

class SomeController
{
	public SomeController(IConfiguration config, SomeService service) {}
}

class Composition
{
    private static void Setup() =>
        DI.Setup(nameof(Composition))
	.Arg<IConfiguration>()
        .Bind<SomeService>().To<SomeService>()
        .Root<SomeController>();
}

Not ideal ofc but I'd prefer this way rather than disabled checks, not sure if there are other people like me though 😄

Btw, there is also StrongInject way. Below are both controller-based and minimal (without top level check):

public interface IBarService
{
    string Bar();
}

public class BarService : IBarService
{
    public string Bar() => "bar";
}

public interface IGreetingService
{
    string Hello();
}

public class GreetingService : IGreetingService
{
    public string Hello()
    {
        return "world";
    }
}

public class FooController
{
    private readonly IBarService _barService;

    public FooController(IBarService barService)
    {
        _barService = barService;
    }

    [HttpGet]
    public string Foo() => _barService.Bar();
}

Registration & configuration:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers().ResolveThroughServiceProvider();
new Composition().Register((type, svc) => builder.Services.AddTransient(type, svc));
var app = builder.Build();
app.MapGet("/hello", (IGreetingService service) => service.Hello());
app.MapControllers();
app.Run();

internal partial class Composition
{
    private static void Setup() =>
        DI.Setup(nameof(Composition))
        .Bind<IGreetingService>().To<GreetingService>().Root<IGreetingService>()
        .Bind<IBarService>().To<BarService>().Root<FooController>();
}

internal static class Extensions
{
    public static IMvcBuilder ResolveThroughServiceProvider(this IMvcBuilder builder)
    {
        builder.PartManager.PopulateFeature(new ControllerFeature());
        builder.Services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());
        return builder;
    }

    public static void Register(this Composition composition, Action<Type, Func<IServiceProvider, object>> registration)
    {
        // Iterate over the container roots
        foreach (var rootProp in composition.GetType().GetProperties())
        {
            registration(rootProp.PropertyType, _ => composition.Resolve(rootProp.PropertyType));
        }
    }
}

However, IServiceProviderFactory is still a more powerful way.

Thank you Nikolay, I see it is now documented. This is exactly what I need!
https://github.com/DevTeam/Pure.DI/blob/master/readme/check-for-a-root.md