From 435fe5ec963ed3da0ddb31d730b73c55bd2fe309 Mon Sep 17 00:00:00 2001 From: Khoa Date: Tue, 7 Oct 2025 13:45:51 +0700 Subject: [PATCH] MET-4355 ioc break startup --- src/.dockerignore | 30 ++++++++++ src/ApiTest/ApiTest.csproj | 20 +++++++ src/ApiTest/ApiTest.http | 6 ++ .../Controllers/WeatherForecastController.cs | 37 ++++++++++++ src/ApiTest/Dockerfile | 30 ++++++++++ src/ApiTest/Program.cs | 33 ++++++++++ src/ApiTest/Services/ChildClassTest.cs | 10 ++++ src/ApiTest/Services/ParentClassTest.cs | 23 +++++++ src/ApiTest/WeatherForecast.cs | 13 ++++ src/ApiTest/appsettings.Development.json | 8 +++ src/ApiTest/appsettings.json | 10 ++++ .../DependencyResolverTest.cs | 18 ++++++ .../ClassContainsDefaultLifetime.cs | 22 +++++++ .../InjectedClass/ClassDefaultLifetime.cs | 7 ++- src/Samhammer.DependencyInjection.sln | 10 +++- .../Attributes/InjectAbAttribute.cs | 22 +++++++ .../Attributes/InjectAbWithFlagAttribute.cs | 25 ++++++++ ...njectAbMatchingServiceDescriptorHandler.cs | 49 +++++++++++++++ ...ConfigsMatchingServiceDescriptorHandler.cs | 60 +++++++++++++++++++ .../Samhammer.DependencyInjection.csproj | 1 + .../ServiceCollectionExtensions.cs | 2 + 21 files changed, 433 insertions(+), 3 deletions(-) create mode 100644 src/.dockerignore create mode 100644 src/ApiTest/ApiTest.csproj create mode 100644 src/ApiTest/ApiTest.http create mode 100644 src/ApiTest/Controllers/WeatherForecastController.cs create mode 100644 src/ApiTest/Dockerfile create mode 100644 src/ApiTest/Program.cs create mode 100644 src/ApiTest/Services/ChildClassTest.cs create mode 100644 src/ApiTest/Services/ParentClassTest.cs create mode 100644 src/ApiTest/WeatherForecast.cs create mode 100644 src/ApiTest/appsettings.Development.json create mode 100644 src/ApiTest/appsettings.json create mode 100644 src/Samhammer.DependencyInjection.Test/TestData/InjectedClass/ClassContainsDefaultLifetime.cs create mode 100644 src/Samhammer.DependencyInjection/Attributes/InjectAbAttribute.cs create mode 100644 src/Samhammer.DependencyInjection/Attributes/InjectAbWithFlagAttribute.cs create mode 100644 src/Samhammer.DependencyInjection/Handlers/InjectAbMatchingServiceDescriptorHandler.cs create mode 100644 src/Samhammer.DependencyInjection/Handlers/InjectAbValidateConfigsMatchingServiceDescriptorHandler.cs diff --git a/src/.dockerignore b/src/.dockerignore new file mode 100644 index 0000000..fe1152b --- /dev/null +++ b/src/.dockerignore @@ -0,0 +1,30 @@ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md +!**/.gitignore +!.git/HEAD +!.git/config +!.git/packed-refs +!.git/refs/heads/** \ No newline at end of file diff --git a/src/ApiTest/ApiTest.csproj b/src/ApiTest/ApiTest.csproj new file mode 100644 index 0000000..9191e8d --- /dev/null +++ b/src/ApiTest/ApiTest.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + enable + enable + 9a4c2a0c-d9b0-47d9-8bf6-156509435f06 + Linux + + + + + + + + + + + + diff --git a/src/ApiTest/ApiTest.http b/src/ApiTest/ApiTest.http new file mode 100644 index 0000000..ba81544 --- /dev/null +++ b/src/ApiTest/ApiTest.http @@ -0,0 +1,6 @@ +@ApiTest_HostAddress = http://localhost:5025 + +GET {{ApiTest_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/src/ApiTest/Controllers/WeatherForecastController.cs b/src/ApiTest/Controllers/WeatherForecastController.cs new file mode 100644 index 0000000..8151157 --- /dev/null +++ b/src/ApiTest/Controllers/WeatherForecastController.cs @@ -0,0 +1,37 @@ +using ApiTest.Services; +using Microsoft.AspNetCore.Mvc; + +namespace ApiTest.Controllers +{ + [ApiController] + [Route("[controller]")] + public class WeatherForecastController : ControllerBase + { + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + private readonly IParentClassTest _parentTest; + + public WeatherForecastController(ILogger logger, IParentClassTest parentClassTest) + { + _logger = logger; + _parentTest = parentClassTest; + } + + [HttpGet(Name = "GetWeatherForecast")] + public IEnumerable Get() + { + _parentTest.PrintSomething(); + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray(); + } + } +} diff --git a/src/ApiTest/Dockerfile b/src/ApiTest/Dockerfile new file mode 100644 index 0000000..eaff08e --- /dev/null +++ b/src/ApiTest/Dockerfile @@ -0,0 +1,30 @@ +# See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. + +# This stage is used when running from VS in fast mode (Default for Debug configuration) +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +USER app +WORKDIR /app +EXPOSE 8080 +EXPOSE 8081 + + +# This stage is used to build the service project +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["ApiTest/ApiTest.csproj", "ApiTest/"] +RUN dotnet restore "./ApiTest/ApiTest.csproj" +COPY . . +WORKDIR "/src/ApiTest" +RUN dotnet build "./ApiTest.csproj" -c $BUILD_CONFIGURATION -o /app/build + +# This stage is used to publish the service project to be copied to the final stage +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./ApiTest.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration) +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "ApiTest.dll"] \ No newline at end of file diff --git a/src/ApiTest/Program.cs b/src/ApiTest/Program.cs new file mode 100644 index 0000000..92c8924 --- /dev/null +++ b/src/ApiTest/Program.cs @@ -0,0 +1,33 @@ +using ApiTest.Services; +using Samhammer.DependencyInjection; + +var builder = WebApplication.CreateBuilder(args); +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +builder.Host.ConfigureServices(services => services.ResolveDependencies()); +builder.Host.UseDefaultServiceProvider(options => +{ + options.ValidateOnBuild = true; +}); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/src/ApiTest/Services/ChildClassTest.cs b/src/ApiTest/Services/ChildClassTest.cs new file mode 100644 index 0000000..b56a45c --- /dev/null +++ b/src/ApiTest/Services/ChildClassTest.cs @@ -0,0 +1,10 @@ +using Samhammer.DependencyInjection.Attributes; + +namespace ApiTest.Services +{ + public class ChildClassTest : IChildClassTest + { + } + + public interface IChildClassTest { } +} diff --git a/src/ApiTest/Services/ParentClassTest.cs b/src/ApiTest/Services/ParentClassTest.cs new file mode 100644 index 0000000..80534d4 --- /dev/null +++ b/src/ApiTest/Services/ParentClassTest.cs @@ -0,0 +1,23 @@ +using Samhammer.DependencyInjection.Attributes; + +namespace ApiTest.Services +{ + [InjectAb] + public class ParentClassTest : IParentClassTest + { + public ParentClassTest(IChildClassTest childClassTest) + { + Console.WriteLine("Creating test class"); + } + + public void PrintSomething() + { + Console.WriteLine("Printing in test class"); + } + } + + public interface IParentClassTest + { + public void PrintSomething(); + } +} diff --git a/src/ApiTest/WeatherForecast.cs b/src/ApiTest/WeatherForecast.cs new file mode 100644 index 0000000..293ad0b --- /dev/null +++ b/src/ApiTest/WeatherForecast.cs @@ -0,0 +1,13 @@ +namespace ApiTest +{ + public class WeatherForecast + { + public DateOnly Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } + } +} diff --git a/src/ApiTest/appsettings.Development.json b/src/ApiTest/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/src/ApiTest/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/ApiTest/appsettings.json b/src/ApiTest/appsettings.json new file mode 100644 index 0000000..738f12a --- /dev/null +++ b/src/ApiTest/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "EnabledTest": true +} diff --git a/src/Samhammer.DependencyInjection.Test/DependencyResolverTest.cs b/src/Samhammer.DependencyInjection.Test/DependencyResolverTest.cs index 97213fc..f128b66 100644 --- a/src/Samhammer.DependencyInjection.Test/DependencyResolverTest.cs +++ b/src/Samhammer.DependencyInjection.Test/DependencyResolverTest.cs @@ -23,6 +23,24 @@ public DependencyResolverTest() serviceCollection.AddLogging(builder => builder.ClearProviders()); } + [Fact] + private void GetClassContains_WithDefaultLifetime_Scoped() + { + // act + serviceCollection.ResolveDependencies(); + serviceProvider = serviceCollection.BuildServiceProvider(); + + IClassContainsDefaultLifetime service; + + using (var scope = serviceProvider.CreateScope()) + { + service = scope.ServiceProvider.GetService(); + } + + // assert + service.Should().NotBeNull().And.BeOfType(); + } + [Fact] private void GetClass_WithDefaultLifetime_Scoped() { diff --git a/src/Samhammer.DependencyInjection.Test/TestData/InjectedClass/ClassContainsDefaultLifetime.cs b/src/Samhammer.DependencyInjection.Test/TestData/InjectedClass/ClassContainsDefaultLifetime.cs new file mode 100644 index 0000000..ae6824a --- /dev/null +++ b/src/Samhammer.DependencyInjection.Test/TestData/InjectedClass/ClassContainsDefaultLifetime.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Samhammer.DependencyInjection.Attributes; + +namespace Samhammer.DependencyInjection.Test.TestData.InjectedClass +{ + [Inject] + public class ClassContainsDefaultLifetime : IClassContainsDefaultLifetime + { + public ClassContainsDefaultLifetime(IClassDefaultLifetime classDefault) + { + Console.WriteLine($"Creating service {classDefault.ToString()}"); + } + } + + public interface IClassContainsDefaultLifetime + { + } +} diff --git a/src/Samhammer.DependencyInjection.Test/TestData/InjectedClass/ClassDefaultLifetime.cs b/src/Samhammer.DependencyInjection.Test/TestData/InjectedClass/ClassDefaultLifetime.cs index 5fa4d65..9d4a139 100644 --- a/src/Samhammer.DependencyInjection.Test/TestData/InjectedClass/ClassDefaultLifetime.cs +++ b/src/Samhammer.DependencyInjection.Test/TestData/InjectedClass/ClassDefaultLifetime.cs @@ -1,10 +1,15 @@ -using Samhammer.DependencyInjection.Attributes; +using System; +using Samhammer.DependencyInjection.Attributes; namespace Samhammer.DependencyInjection.Test.TestData.InjectedClass { [Inject] public class ClassDefaultLifetime : IClassDefaultLifetime { + public ClassDefaultLifetime() + { + Console.WriteLine("Creating service"); + } } public interface IClassDefaultLifetime diff --git a/src/Samhammer.DependencyInjection.sln b/src/Samhammer.DependencyInjection.sln index 0bf8098..695a436 100644 --- a/src/Samhammer.DependencyInjection.sln +++ b/src/Samhammer.DependencyInjection.sln @@ -12,9 +12,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\README.md = ..\README.md EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samhammer.DependencyInjection.Override", "Samhammer.DependencyInjection.Override\Samhammer.DependencyInjection.Override.csproj", "{3229D5A2-838F-433A-BC7D-32D76A08CCD2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samhammer.DependencyInjection.Override", "Samhammer.DependencyInjection.Override\Samhammer.DependencyInjection.Override.csproj", "{3229D5A2-838F-433A-BC7D-32D76A08CCD2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samhammer.DependencyInjection.Override.Test", "Samhammer.DependencyInjection.Override.Test\Samhammer.DependencyInjection.Override.Test.csproj", "{E5197D26-077D-46AD-92BE-32C3DB192C44}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samhammer.DependencyInjection.Override.Test", "Samhammer.DependencyInjection.Override.Test\Samhammer.DependencyInjection.Override.Test.csproj", "{E5197D26-077D-46AD-92BE-32C3DB192C44}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiTest", "ApiTest\ApiTest.csproj", "{6A652F59-57F5-4CA3-B552-4A57EC9A42F9}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -38,6 +40,10 @@ Global {E5197D26-077D-46AD-92BE-32C3DB192C44}.Debug|Any CPU.Build.0 = Debug|Any CPU {E5197D26-077D-46AD-92BE-32C3DB192C44}.Release|Any CPU.ActiveCfg = Release|Any CPU {E5197D26-077D-46AD-92BE-32C3DB192C44}.Release|Any CPU.Build.0 = Release|Any CPU + {6A652F59-57F5-4CA3-B552-4A57EC9A42F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6A652F59-57F5-4CA3-B552-4A57EC9A42F9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6A652F59-57F5-4CA3-B552-4A57EC9A42F9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6A652F59-57F5-4CA3-B552-4A57EC9A42F9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Samhammer.DependencyInjection/Attributes/InjectAbAttribute.cs b/src/Samhammer.DependencyInjection/Attributes/InjectAbAttribute.cs new file mode 100644 index 0000000..b19106d --- /dev/null +++ b/src/Samhammer.DependencyInjection/Attributes/InjectAbAttribute.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Samhammer.DependencyInjection.Attributes; + +namespace Samhammer.DependencyInjection.Attributes +{ + [AttributeUsage(AttributeTargets.Class)] + public class InjectAbAttribute : DependencyInjectionAttribute + { + public Target Target { get; } + + public InjectAbAttribute(Target target = Target.Matching, ServiceLifetime lifetime = ServiceLifetime.Scoped) + : base(lifetime) + { + Target = target; + } + } +} diff --git a/src/Samhammer.DependencyInjection/Attributes/InjectAbWithFlagAttribute.cs b/src/Samhammer.DependencyInjection/Attributes/InjectAbWithFlagAttribute.cs new file mode 100644 index 0000000..7d34d5f --- /dev/null +++ b/src/Samhammer.DependencyInjection/Attributes/InjectAbWithFlagAttribute.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Samhammer.DependencyInjection.Attributes; + +namespace Samhammer.DependencyInjection.Attributes +{ + [AttributeUsage(AttributeTargets.Class)] + public class InjectAbWithConfigsAttribute : DependencyInjectionAttribute + { + public Target Target { get; } + + public string ConfigSections { get; set; } + + public InjectAbWithConfigsAttribute(string configSections, Target target = Target.Matching, ServiceLifetime lifetime = ServiceLifetime.Scoped) + : base(lifetime) + { + Target = target; + ConfigSections = configSections; + } + } +} diff --git a/src/Samhammer.DependencyInjection/Handlers/InjectAbMatchingServiceDescriptorHandler.cs b/src/Samhammer.DependencyInjection/Handlers/InjectAbMatchingServiceDescriptorHandler.cs new file mode 100644 index 0000000..39a8725 --- /dev/null +++ b/src/Samhammer.DependencyInjection/Handlers/InjectAbMatchingServiceDescriptorHandler.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; +using Samhammer.DependencyInjection; +using Samhammer.DependencyInjection.Attributes; +using Samhammer.DependencyInjection.Handlers; + +namespace Samhammer.DependencyInjection.Handlers +{ + public class InjectAbMatchingServiceDescriptorHandler : AttributeServiceDescriptorHandler + { + private DependencyResolverOptions Options { get; } + + public InjectAbMatchingServiceDescriptorHandler(DependencyResolverOptions options) + { + Options = options; + } + + public override bool MatchAdditionalCriteria(InjectAbAttribute attribute) + { + return attribute.Target == Target.Matching; + } + + public override IEnumerable ResolveServices(Type implementationType, InjectAbAttribute injectAttribute) + { + var serviceType = GetMatchingInterfaceType(implementationType, injectAttribute); + var serviceDescriptor = new ServiceDescriptor(serviceType, provider => + { + var instance = ActivatorUtilities.CreateInstance(provider, implementationType); + return instance; + }, injectAttribute.LifeTime); + yield return serviceDescriptor; + } + + private Type GetMatchingInterfaceType(Type implementationType, InjectAbAttribute injectAttribute) + { + var matchingInterfaceName = $"I{implementationType.GetTypeInfo().Name}"; + var serviceType = implementationType.GetInterface(matchingInterfaceName); + + if (serviceType == null) + { + throw new ArgumentException($"Class {implementationType} has no matching interface {matchingInterfaceName} defined", nameof(implementationType)); + } + + return serviceType; + } + } +} diff --git a/src/Samhammer.DependencyInjection/Handlers/InjectAbValidateConfigsMatchingServiceDescriptorHandler.cs b/src/Samhammer.DependencyInjection/Handlers/InjectAbValidateConfigsMatchingServiceDescriptorHandler.cs new file mode 100644 index 0000000..8e6f9b2 --- /dev/null +++ b/src/Samhammer.DependencyInjection/Handlers/InjectAbValidateConfigsMatchingServiceDescriptorHandler.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Samhammer.DependencyInjection; +using Samhammer.DependencyInjection.Attributes; +using Samhammer.DependencyInjection.Handlers; + +namespace Samhammer.DependencyInjection.Handlers +{ + public class InjectAbValidateConfigsMatchingServiceDescriptorHandler : AttributeServiceDescriptorHandler + { + private DependencyResolverOptions Options { get; } + + public InjectAbValidateConfigsMatchingServiceDescriptorHandler(DependencyResolverOptions options) + { + Options = options; + } + + public override bool MatchAdditionalCriteria(InjectAbWithConfigsAttribute attribute) + { + return attribute.Target == Target.Matching; + } + + public override IEnumerable ResolveServices(Type implementationType, InjectAbWithConfigsAttribute injectAttribute) + { + var serviceType = GetMatchingInterfaceType(implementationType, injectAttribute); + var serviceDescriptor = new ServiceDescriptor(serviceType, provider => + { + var configuration = provider.GetService(); + var requiredConfigs = injectAttribute.ConfigSections.Split(','); + foreach (var requiredConfig in requiredConfigs) + { + if (string.IsNullOrWhiteSpace(configuration[requiredConfig])) + { + throw new InvalidOperationException($"Please setup config section {requiredConfig} to enable this service"); + } + } + + var instance = ActivatorUtilities.CreateInstance(provider, implementationType); + return instance; + }, injectAttribute.LifeTime); + yield return serviceDescriptor; + } + + private Type GetMatchingInterfaceType(Type implementationType, InjectAbWithConfigsAttribute injectAttribute) + { + var matchingInterfaceName = $"I{implementationType.GetTypeInfo().Name}"; + var serviceType = implementationType.GetInterface(matchingInterfaceName); + + if (serviceType == null) + { + throw new ArgumentException($"Class {implementationType} has no matching interface {matchingInterfaceName} defined", nameof(implementationType)); + } + + return serviceType; + } + } +} diff --git a/src/Samhammer.DependencyInjection/Samhammer.DependencyInjection.csproj b/src/Samhammer.DependencyInjection/Samhammer.DependencyInjection.csproj index 7b4df4a..ba31d1f 100644 --- a/src/Samhammer.DependencyInjection/Samhammer.DependencyInjection.csproj +++ b/src/Samhammer.DependencyInjection/Samhammer.DependencyInjection.csproj @@ -17,6 +17,7 @@ + diff --git a/src/Samhammer.DependencyInjection/ServiceCollectionExtensions.cs b/src/Samhammer.DependencyInjection/ServiceCollectionExtensions.cs index c72209c..16e3275 100644 --- a/src/Samhammer.DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Samhammer.DependencyInjection/ServiceCollectionExtensions.cs @@ -44,6 +44,8 @@ private static DependencyResolverOptions BuildDefaultOptions() options.AddAttributeHandler(logger => new InjectAllServiceDescriptorHandler()); options.AddAttributeHandler(logger => new InjectAsServiceDescriptorHandler()); options.AddAttributeHandler(logger => new InjectMatchingServiceDescriptorHandler(options)); + options.AddAttributeHandler(logger => new InjectAbMatchingServiceDescriptorHandler(options)); + options.AddAttributeHandler(logger => new InjectAbValidateConfigsMatchingServiceDescriptorHandler(options)); options.AddProvider((logger, o) => new AttributeServiceDescriptorProvider(logger, o)); return options;