Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Inject Service Provider To Generic Host #89

Answered by NikolayPianikov
c0nstexpr asked this question in Q&A
Discussion options

I would like to use Pure.DI in generic host and replace the MS service provider to my own composition
But couldn't get it work, the host service provider doesn't contain my dependency.

using System.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
using Pure.DI;
using Pure.DI.MS;
namespace CSharpConsole;
public interface IMyDependency : IDisposable
{
 void Bar() => Console.WriteLine(this);
 void IDisposable.Dispose() { }
}
public class MyDependency : IMyDependency;
public interface IMyService : IDisposable
{
 void Bar() => Console.WriteLine(this);
 void IDisposable.Dispose() { }
}
public class MyService(IMyDependency _) : IMyService;
public partial class Composition :
 ServiceProviderFactory<Composition>,
 IKeyedServiceProvider,
 IServiceScopeFactory,
 IServiceScope
{
 [Conditional(nameof(DI))]
 static void Setup() => DI.Setup()
 .Hint(Hint.ObjectResolveMethodName, nameof(GetService))
 .Hint(Hint.ObjectResolveByTagMethodName, nameof(GetRequiredKeyedService))
 .RootBind<IMyDependency>().As(Lifetime.Singleton).To<MyDependency>()
 .RootBind<IMyService>().As(Lifetime.Singleton).To<MyService>();
 public IServiceProvider ServiceProvider => this;
 public IServiceScope CreateScope() => new Composition(this);
 public object GetKeyedService(Type serviceType, object? serviceKey) =>
 GetRequiredKeyedService(serviceType, serviceKey);
}
using CSharpConsole;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = Host.CreateApplicationBuilder();
builder.ConfigureContainer(new Composition());
var host = builder.Build();
_ = Task.Run(host.Run);
var service = host.Services.GetRequiredService<IMyService>(); // <-- Null Reference Here
service.Bar();
You must be logged in to vote

To register composition roots in MS DI, you must use the Pure.DI setup that are defined in the ServiceProviderFactory<Composition> class. To do this, you can add a dependency on it by calling DependsOn(Base). Here is the working code:

// See https://aka.ms/new-console-template for more information
using System.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
using Pure.DI;
using Pure.DI.MS;
using Microsoft.Extensions.Hosting;
var builder = Host.CreateApplicationBuilder();
builder.ConfigureContainer(new Composition());
var host = builder.Build();
_ = Task.Run(host.Run);
var service = host.Services.GetRequiredService<IMyService>();
service.Bar();
public interface IMyDepen...

Replies: 2 comments 20 replies

Comment options

To register composition roots in MS DI, you must use the Pure.DI setup that are defined in the ServiceProviderFactory<Composition> class. To do this, you can add a dependency on it by calling DependsOn(Base). Here is the working code:

// See https://aka.ms/new-console-template for more information
using System.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
using Pure.DI;
using Pure.DI.MS;
using Microsoft.Extensions.Hosting;
var builder = Host.CreateApplicationBuilder();
builder.ConfigureContainer(new Composition());
var host = builder.Build();
_ = Task.Run(host.Run);
var service = host.Services.GetRequiredService<IMyService>();
service.Bar();
public interface IMyDependency : IDisposable
{
 void Bar() => Console.WriteLine(this);
 void IDisposable.Dispose()
 {
 }
}
public class MyDependency : IMyDependency;
public interface IMyService : IDisposable
{
 void Bar() => Console.WriteLine(this);
 void IDisposable.Dispose()
 {
 }
}
public class MyService(IMyDependency _) : IMyService;
public partial class Composition :
 ServiceProviderFactory<Composition>,
 IKeyedServiceProvider,
 IServiceScopeFactory,
 IServiceScope
{
 [Conditional(nameof(DI))]
 static void Setup() => DI.Setup()
 .DependsOn(Base)
 .Hint(Hint.ObjectResolveMethodName, nameof(GetService))
 .Hint(Hint.ObjectResolveByTagMethodName, nameof(GetRequiredKeyedService))
 .RootBind<IMyDependency>().As(Lifetime.Singleton).To<MyDependency>()
 .RootBind<IMyService>().As(Lifetime.Singleton).To<MyService>();
 public IServiceProvider ServiceProvider => this;
 public IServiceScope CreateScope() => new Composition(this);
 public object GetKeyedService(Type serviceType, object? serviceKey) =>
 GetRequiredKeyedService(serviceType, serviceKey);
}

Your composition setup code can be simplified:

public partial class Composition : ServiceProviderFactory<Composition>
{
 private static void Setup() => DI.Setup()
 .DependsOn(Base)
 .DefaultLifetime(Lifetime.Singleton)
 .Bind().To<MyDependency>()
 .RootBind<IMyService>().To<MyService>();
}
You must be logged in to vote
1 reply
Comment options

I realize that I totally ignore the line .DependsOn(Base), now it works

Answer selected by c0nstexpr
Comment options

@NikolayPianikov I'm working on a MAUI app with ReactorUI framework and am having an issue. I have defined my Composition class properly and am receiving it on the App constructor as in the sample. The problem is that in reactor ui, I'm inheriting from a ReactorApp class and need to send the IServiceProvider to the base constructor.

The problem is that I don't know how to get the IServiceProvider from the Composition that inherits from ServiceProviderFactory<>. I've tried to do something similar to what was discussed here, but ultimately, the compiler asks me to implement the GetService method. Right now, I just have an empty method because I don't have access to the underlying ServiceProvider from the ServiceProviderFactory.

Here's my Composition class:

public partial class Composition : 
 ServiceProviderFactory<Composition>,
 IServiceProvider
{
 private static void Setup() => DI.Setup()
 .DependsOn(Base)
 .Hint(Hint.OnCannotResolveContractTypeNameRegularExpression, "^Microsoft\\.(Extensions|Maui)\\..+$")
 .Root<SplashScreenPage>(nameof(SplashScreenPage))
 .Bind<IAuthenticate>().As(Lifetime.Singleton).To<AndroidAuthenticator>();
 public IServiceProvider ServiceProvider => this; // this throws a stack overflow exception when calling new Composition().ServiceProvider
 public object GetService(Type serviceType)
 {
 // TODO: how to implement this without access to the serviceprovider?
 }
}

Is there something I'm missing?

You must be logged in to vote
19 replies
Comment options

It would be better if you let me take a look at your project. In a call to .Hint(Hint.OnCannotResolve, value) - value can be either On or Off. Did you add the IMyInterface binding to its implementation?

Comment options

sure, I'll get a basic project to repro this

Comment options

Ok, I was finally able to have a minimal code to repro the issue based on your MAUIReactorApp.csproj project:

  1. Add the Microsoft.Extensions.Http nuget package to the project.
  2. Add the following interfaces and implementations to the project:
public interface IApiHelper;
public class ApiHelper(ILogger<ApiHelper> logger, IHttpClientFactory httpClientFactory) : IApiHelper
{
 private readonly ILogger<ApiHelper> _logger = logger;
 private readonly HttpClient _httpClient = httpClientFactory.CreateClient("ApiClient");
}
public interface IService;
public class MyService(IApiHelper apiHelper) : IService
{
 private readonly IApiHelper _apiHelper = apiHelper;
}
  1. Add the following calls right before the line return builder.Build(); on MauiProgram.cs:
builder.Services
 .AddHttpClient()
 .AddSingleton<IApiHelper, ApiHelper>();
  1. Modify the App.xaml.cs class' constructor to receive an IService dependency:
public App(IServiceProvider serviceProvider, IService service)
  1. Update the Composition.cs class by adding the hint and the binding:
private static void Setup() => DI.Setup()
 .DependsOn(Base)
+ .Hint(Hint.OnCannotResolveContractTypeNameWildcard, "*.IApiHelper")
 // Roots
 .Root<Composition>()
+ .Bind<IService>().As(Singleton).To<MyService>()
 // Builders
 .Builder<ClockPage>()
 // View Models
 .Bind().As(Singleton).To<ClockViewModel>()
 // Models
 .Bind().To<Log<TT>>()
 .Bind().As(Singleton).To<Timer>()
 .Bind().As(PerBlock).To<SystemClock>()
 // Infrastructure
 .Bind().To<Dispatcher>();

The compilation fails, and I noticed a few things:

  1. The code generator no longer generates the IDisposable implementation, so I have to remove the using keyword from MauiProgram.cs.
  2. You'll see several 'Unable to resolve' messages for dependencies from the Microsoft.Extensions.* and Maui.* namespaces, which are resolved at runtime.
  3. The generator does not generate the BuildUp method, possibly due to the hint?

I don't know how to resolve this, but it shows the issue I'm currently facing.

Thanks for all your help!

Comment options

I made a thread with an example with your changes.

It is better to use a single dependency resolution mechanism

builder.Services
 .AddHttpClient()
 .AddSingleton<IApiHelper, ApiHelper>();

i.e. you can migrate:

partial class Composition: ServiceProviderFactory<Composition>
{
 private static void Setup() => DI.Setup()
...
 .Bind().As(Singleton).To<ApiHelper>()
... 
}

Right now, hint inheritance is not expected and you have to redefine everything:

.Hint(Hint.OnCannotResolveContractTypeNameWildcard, "Microsoft.Extensions.*")
.Hint(Hint.OnCannotResolveContractTypeNameWildcard, "Microsoft.AspNetCore.*")
.Hint(Hint.OnCannotResolveContractTypeNameWildcard, "Microsoft.Maui.*")
.Hint(Hint.OnCannotResolveContractTypeNameWildcard, "System.Net.Http*")

But since version 2.1.66 I have added inheritance, and you can do it like this:

.Hint(Hint.OnCannotResolveContractTypeNameWildcard, "System.Net.Http*")

Only composition roots (regular or anonymous) can be resolved via IServiceProvider so need a .Root<IService>():

partial class Composition: ServiceProviderFactory<Composition>
{
 // IMPORTANT:
 // Only composition roots (regular or anonymous) can be resolved through the `IServiceProvider` interface.
 // These roots must be registered using `Root(...)` or `RootBind()` calls.
 private static void Setup() => DI.Setup()
...
 .Root<IService>()
 .Bind().As(Singleton).To<MyService>()
...
}
Comment options

Thanks, I'll be trying this out soon. By the way, thanks also for the new .66 update!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
Q&A
Labels
None yet

AltStyle によって変換されたページ (->オリジナル) /