dadhi / DryIoc

DryIoc is fast, small, full-featured IoC Container for .NET
MIT License
1.01k stars 123 forks source link

IOption pattern in DryIoc #522

Closed sramananthula closed 1 year ago

sramananthula commented 2 years ago

In ASP.Net, IOption pattern used for read config values from appsettings.json and populate concrete class. That is injected where ever required. So it would be great if we have such with DryIoc pattern.

Expectation is if could achieve with Dry Ioc pattern it would be great.

services.Configure( configurationRoot.GetSection( key: nameof(TransientFaultHandlingOptions)));

Here is the IOption pattern applied in ASP.Net, (https://docs.microsoft.com/en-us/dotnet/core/extensions/options)

For example, to read the configuration values from an appsettings.json file: {
"TransientFaultHandlingOptions": { "Enabled": true, "AutoRetryDelay": "00:00:07" } }

Create the following TransientFaultHandlingOptions class for the above setting.

public class TransientFaultHandlingOptions { public bool Enabled { get; set; } public TimeSpan AutoRetryDelay { get; set; } }

In the program.cs, (application start up) read the configuration.

IConfigurationRoot configurationRoot = configuration.Build();

    services.Configure<TransientFaultHandlingOptions>(
configurationRoot.GetSection(
    key: nameof(TransientFaultHandlingOptions)));

And the consumer uses these settings like below:

public class ExampleService { private readonly TransientFaultHandlingOptions _options;

public ExampleService**(IOptions<TransientFaultHandlingOptions> options**) =>
    _options = options.Value;

}

dadhi commented 2 years ago

@sramananthula Hi,

So, are the options not injected with the current DryIoc adapter?

dadhi commented 2 years ago

I have tested it in the example project for #520 (https://github.com/dadhi/DryIoc/blob/9ea3bd9a2a58034792a0df30ecfe86af2ffdd574/samples/GHIssue520/Program.cs#L11), and DryIoc has injected the options just fine https://github.com/dadhi/DryIoc/blob/9ea3bd9a2a58034792a0df30ecfe86af2ffdd574/samples/GHIssue520/Program.cs#L169

sramananthula commented 2 years ago

Hi, I tested with my project and it is giving following exception.

DryIoc.MefAttributedModel.AttributedModelException HResult=0x80131509 Message=code: Error._containerErrorCount; message: Unable to find single constructor nor marked with System.ComponentModel.Composition.ImportingConstructorAttribute nor default constructor in Microsoft.Extensions.Options.OptionsFactory when resolving: Microsoft.Extensions.Options.OptionsFactory: Microsoft.Extensions.Options.IOptionsFactory as parameter "factory" FactoryId=331 (IsSingletonOrDependencyOfSingleton) in Singleton Microsoft.Extensions.Options.UnnamedOptionsManager: Microsoft.Extensions.Options.IOptions as parameter "options" FactoryId=330 (IsSingletonOrDependencyOfSingleton) in resolution root Singleton Fmr.SuperNova.OptionService FactoryId=329 (IsSingletonOrDependencyOfSingleton, IsResolutionCall) from container without scope with Rules with {TrackingDisposableTransients} and without {ThrowOnRegisteringDisposableTransient} with DefaultReuse=Singleton {Lifespan=1000} with Made={FactoryMethod=, PropertiesAndFields=, ParameterSelector=} Source=DryIoc.MefAttributedModel StackTrace: at DryIoc.MefAttributedModel.AttributedModel.GetImportingConstructor(Request request, Object fallbackMethodOrSelector) at DryIoc.ReflectionFactory.CreateExpressionOrDefault(Request request) at DryIoc.Factory.GetExpressionOrDefault(Request request) at DryIoc.ReflectionFactory.CreateExpressionOrDefault(Request request) at DryIoc.Factory.GetExpressionOrDefault(Request request) at DryIoc.ReflectionFactory.CreateExpressionOrDefault(Request request) at DryIoc.Factory.GetExpressionOrDefault(Request request) at DryIoc.Container.ResolveAndCache(Int32 serviceTypeHash, Type serviceType, IfUnresolved ifUnresolved) at DryIoc.Container.DryIoc.IResolver.Resolve(Type serviceType, IfUnresolved ifUnresolved) at Fmr.BaseLibrary.IocProvider.Resolve[T]() in C:\Users\a693097\source\repos\unified-service-design-poc\Fmr.BaseLibrary\IocProvider.cs:line 69.

Here is my code: public class MyConfigurationProvider : IConfigurationProvider {

//Constructor
public MyConfigurationProvider([Import]IOptions<Settings> options)
    {
        _options = options.Value;
    }

}

///Option Class public class Settings { public string URL{ get; set; }

    public string KeyOne { get; set; }
}

//appsettings.json { "Settings": { "ChartIQ": "http://iichart.fmr.com/branch/develop/pure/examples/chart.html", "KeyOne": "I am from config settings" } }

//configure options builder.Services.Configure(builder.Configuration.GetSection("Settings"));

//Failing here if the constructor takes IOptions. It resolves with out any issues if constructor does not take //IOPtions<> var optionService = Container.Resolve();

dadhi commented 2 years ago

I see that you are using MEF extension? And here is the thing that you do not control (you cannot put the Export on top of it) and should Register it: Microsoft.Extensions.Options.OptionsFactory<Fmr.SuperNova.Settings>. Try to register OptionFactory<> in container and specify what constructor to use via made: Made.Of(optionFactoryConstructor) parameter.

sramananthula commented 2 years ago

for each option do we need to register this way then along with get section from config ?

Instance.Container.Register<Microsoft.Extensions.Options.IOptionsFactory>(); builder.Services.Configure(builder.Configuration.GetSection(key: nameof(Settings)));

sramananthula commented 2 years ago

Instance.Container.Register();

DryIoc.ContainerException HResult=0x80131509 Message=code: Error.RegisteringAbstractImplementationTypeAndNoFactoryMethod; message: Registering abstract implementation type Microsoft.Extensions.Options.IOptionsFactory when it is should be concrete. Also there is not FactoryMethod to use instead. Source=DryIoc StackTrace: at DryIoc.Throw.It(Int32 error, Object arg0, Object arg1, Object arg2, Object arg3) at DryIoc.ReflectionFactory.ValidateImplementationType(Type type) at DryIoc.ReflectionFactory.Of(Type implementationType, IReuse reuse, Made made, Setup setup) at DryIoc.Registrator.Register[TService,TImplementation](IRegistrator registrator, IReuse reuse, Made made, Setup setup, Nullable`1 ifAlreadyRegistered, Object serviceKey)

dadhi commented 2 years ago

@sramananthula You are registering the interface instead of the implementation, that's why you are getting the error.

Looking at the MS docs https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-6.0#use-di-services-to-configure-options you may just call AddOptions to add them to services.

Btw, I don't see anywhere in this issue that you're doing this.

sramananthula commented 2 years ago

I registered Container.Register<Microsoft.Extensions.Options.OptionsFactory>(); and also did builder.Services.AddOptions(); builder.Services.Configure(builder.Configuration.GetSection(key: nameof(Settings))); But my class is not getting resolved though. Getting an error on .Resolve(); public class OptionService : IOptionService { private Settings _options;

    public OptionService(IOptions<Settings> options) 
    {
        _options = options.Value;
    }
}
dadhi commented 2 years ago

@sramananthula What is the error?

sramananthula commented 2 years ago

Sorry. i did not added madeof the container register method. I just added like this. I am not sure whether it is correct or not.

Container.Register<OptionsFactory>(made: Made.Of(req => typeof(OptionsFactory)));

here is the error: DryIoc.ContainerException HResult=0x80131509 Message=code: Error.UnableToResolveUnknownService; message: Unable to resolve Microsoft.Extensions.Options.IOptions as parameter "options" in resolution root Fmr.SuperNova.OptionService: Fmr.SuperNova.IOptionService FactoryId=568 from container without scope with Rules with {TrackingDisposableTransients} and without {ThrowOnRegisteringDisposableTransient} with DefaultReuse=Singleton {Lifespan=1000} with Made={FactoryMethod=, PropertiesAndFields=, ParameterSelector=} Where no service registrations found and no dynamic registrations found in 0 of Rules.DynamicServiceProviders and nothing found in 0 of Rules.UnknownServiceResolvers Source=DryIoc StackTrace: at DryIoc.Throw.It(Int32 error, Object arg0, Object arg1, Object arg2, Object arg3) at DryIoc.Container.TryThrowUnableToResolve(Request request) at DryIoc.Container.DryIoc.IContainer.ResolveFactory(Request request) at DryIoc.ReflectionFactory.CreateExpressionOrDefault(Request request) at DryIoc.Factory.GetExpressionOrDefault(Request request) at DryIoc.Container.ResolveAndCache(Int32 serviceTypeHash, Type serviceType, IfUnresolved ifUnresolved) at DryIoc.Container.DryIoc.IResolver.Resolve(Type serviceType, IfUnresolved ifUnresolved)

dadhi commented 2 years ago

@sramananthula Ok, You are using the Made.Of wrong. Read the docs.

Regarding the initial problem with options... I don't know what is wrong with your case, but I have the working example here.

You may

You may try to run it in isolation as well by replacing the line test.csproj

<ProjectReference Include="..\..\src\DryIoc.Microsoft.DependencyInjection\DryIoc.Microsoft.DependencyInjection.csproj" />

with

<PackageReference Include="DryIoc.Microsoft.DependencyInjection" Version="6.1.0" />
dadhi commented 1 year ago

Hope it will be fixed for you @sramananthula by #544