-
Notifications
You must be signed in to change notification settings - Fork 29
Inject Service Provider To Generic Host #89
-
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();
Beta Was this translation helpful? Give feedback.
All reactions
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
-
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>(); }
Beta Was this translation helpful? Give feedback.
All reactions
-
I realize that I totally ignore the line .DependsOn(Base), now it works
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1
-
@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?
Beta Was this translation helpful? Give feedback.
All reactions
-
👀 1
-
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?
Beta Was this translation helpful? Give feedback.
All reactions
-
sure, I'll get a basic project to repro this
Beta Was this translation helpful? Give feedback.
All reactions
-
Ok, I was finally able to have a minimal code to repro the issue based on your MAUIReactorApp.csproj project:
- Add the
Microsoft.Extensions.Httpnuget package to the project. - 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; }
- Add the following calls right before the line
return builder.Build();onMauiProgram.cs:
builder.Services .AddHttpClient() .AddSingleton<IApiHelper, ApiHelper>();
- Modify the
App.xaml.csclass' constructor to receive anIServicedependency:
public App(IServiceProvider serviceProvider, IService service)
- Update the
Composition.csclass 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:
- The code generator no longer generates the
IDisposableimplementation, so I have to remove theusingkeyword fromMauiProgram.cs. - You'll see several 'Unable to resolve' messages for dependencies from the
Microsoft.Extensions.*andMaui.*namespaces, which are resolved at runtime. - 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!
Beta Was this translation helpful? Give feedback.
All reactions
-
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>() ... }
Beta Was this translation helpful? Give feedback.
All reactions
-
Thanks, I'll be trying this out soon. By the way, thanks also for the new .66 update!
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1