Repository: Finbuckle/Finbuckle.MultiTenant (2025-08-04T18:51:00Z)

Issue:
Multiple `IAuthenticationSchemeProvider` not supported - Incompatibility with Duende.BFF V4 wich adds a second one
When we add per tenant authentication, `IAuthenticationSchemeProvider` is decorated in `WithPerTenantAuthenticationCore()`

But the `DecorateService` extension method only support single service registration: 

```csharp
    public static bool DecorateService<TService, TImpl>(this IServiceCollection services, params object[] parameters)
    {
        var existingService = services.SingleOrDefault(s => s.ServiceType == typeof(TService));
```

Duende BFF adds a second `IAuthenticationSchemeProvider` in V4 for its new feature "dynamic frontends":

```csharp
        // Add a scheme provider that will inject authentication schemes that are needed for the BFF
        builder.Services.AddTransient<IAuthenticationSchemeProvider, BffAuthenticationSchemeProvider>();
```

This throws an exception when SingleOrDefault is called.


Discussion:
hi @LionelVallet 

I see what you mean there. Do you know how they user the multiple implementations or do they really only use the last?

I can modify the decorator to replace the last instance found since that is what DI usually uses. That would work fine for ASP.NET Core and most likely BFF. Do you know if the original implementation from .NET is still used once this implementation is registered?
I’m not sure, so I asked the question here: https://github.com/orgs/DuendeSoftware/discussions/288
Would it be problematic to decorate all registered `IAuthenticationSchemeProvider` implementations? Other libraries or projects might add a second, third, and so on. This isn’t specific to this particular scenario.
Yeah I just submitted a PR to address this, found a few bugs in testing and fixed them too. Thanks for reporting this. Will release with the next version soon.
Thanks to you for this quick fix and your awesome library!
Hi @AndrewTriesToCode, do you have any idea when the next release is scheduled?
Hi, planning on tomorrow. 
:tada: This issue has been resolved in version 9.3.0 :tada:

The release is available on [GitHub release](https://github.com/Finbuckle/Finbuckle.MultiTenant/releases/tag/v9.3.0)

Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:



Patch:
diff --git a/src/Finbuckle.MultiTenant/DependencyInjection/ServiceCollectionExtensions.cs b/src/Finbuckle.MultiTenant/DependencyInjection/ServiceCollectionExtensions.cs
index c17514d1..aa2a8b63 100644
--- a/src/Finbuckle.MultiTenant/DependencyInjection/ServiceCollectionExtensions.cs
+++ b/src/Finbuckle.MultiTenant/DependencyInjection/ServiceCollectionExtensions.cs
@@ -62,64 +62,79 @@ public static MultiTenantBuilder<TTenantInfo> AddMultiTenant<TTenantInfo>(this I
     // TODO: better document and extract
     public static bool DecorateService<TService, TImpl>(this IServiceCollection services, params object[] parameters)
     {
-        var existingService = services.SingleOrDefault(s => s.ServiceType == typeof(TService));
-        if (existingService is null)
+        var existingServices = services.Where(s => s.ServiceType == typeof(TService)).ToList();
+        if (existingServices.Count == 0)
             throw new ArgumentException($"No service of type {typeof(TService).Name} found.");
 
         ServiceDescriptor? newService;
 
-        if (existingService.ImplementationType is not null)
+        foreach (var existingService in existingServices)
         {
-            newService = new ServiceDescriptor(existingService.ServiceType,
-                sp =>
-                {
-                    TService inner =
-                        (TService)ActivatorUtilities.CreateInstance(sp, existingService.ImplementationType);
+            if (existingService.ImplementationType is not null)
+            {
+                newService = new ServiceDescriptor(existingService.ServiceType,
+                    sp =>
+                    {
+                        TService inner =
+                            (TService)ActivatorUtilities.CreateInstance(sp, existingService.ImplementationType);
 
-                    if (inner is null)
-                        throw new Exception(
-                            $"Unable to instantiate decorated type via implementation type {existingService.ImplementationType.Name}.");
+                        if (inner is null)
+                            throw new Exception(
+                                $"Unable to instantiate decorated type via implementation type {existingService.ImplementationType.Name}.");
 
-                    var parameters2 = new object[parameters.Length + 1];
-                    Array.Copy(parameters, 0, parameters2, 1, parameters.Length);
-                    parameters2[0] = inner;
+                        var parameters2 = new object[parameters.Length + 1];
+                        Array.Copy(parameters, 0, parameters2, 1, parameters.Length);
+                        parameters2[0] = inner;
 
-                    return ActivatorUtilities.CreateInstance<TImpl>(sp, parameters2)!;
-                },
-                existingService.Lifetime);
-        }
-        else if (existingService.ImplementationInstance is not null)
-        {
-            newService = new ServiceDescriptor(existingService.ServiceType,
-                sp =>
-                {
-                    TService inner = (TService)existingService.ImplementationInstance;
-                    return ActivatorUtilities.CreateInstance<TImpl>(sp, inner, parameters)!;
-                },
-                existingService.Lifetime);
-        }
-        else if (existingService.ImplementationFactory is not null)
-        {
-            newService = new ServiceDescriptor(existingService.ServiceType,
-                sp =>
-                {
-                    TService inner = (TService)existingService.ImplementationFactory(sp);
-                    if (inner is null)
-                        throw new Exception(
-                            "Unable to instantiate decorated type via implementation factory.");
+                        return ActivatorUtilities.CreateInstance<TImpl>(sp, parameters2)!;
+                    },
+                    existingService.Lifetime);
+            }
+            else if (existingService.ImplementationInstance is not null)
+            {
+                newService = new ServiceDescriptor(existingService.ServiceType,
+                    sp =>
+                    {
+                        TService inner = (TService)existingService.ImplementationInstance;
+                        if (inner is null)
+                            throw new Exception(
+                                $"Unable to instantiate decorated type via implementation instance of type {existingService.ImplementationInstance.GetType().Name}.");
+                        
+                        var parameters2 = new object[parameters.Length + 1];
+                        Array.Copy(parameters, 0, parameters2, 1, parameters.Length);
+                        parameters2[0] = inner;
+                        
+                        return ActivatorUtilities.CreateInstance<TImpl>(sp, parameters2)!;
+                    },
+                    existingService.Lifetime);
+            }
+            else if (existingService.ImplementationFactory is not null)
+            {
+                newService = new ServiceDescriptor(existingService.ServiceType,
+                    sp =>
+                    {
+                        TService inner = (TService)existingService.ImplementationFactory(sp);
+                        if (inner is null)
+                            throw new Exception(
+                                $"Unable to instantiate decorated type via implementation factory for type {existingService.ServiceType}.");
 
-                    return ActivatorUtilities.CreateInstance<TImpl>(sp, inner, parameters)!;
-                },
-                existingService.Lifetime);
-        }
-        else
-        {
-            throw new Exception(
-                "Unable to instantiate decorated type.");
-        }
+                        var parameters2 = new object[parameters.Length + 1];
+                        Array.Copy(parameters, 0, parameters2, 1, parameters.Length);
+                        parameters2[0] = inner;
+                        
+                        return ActivatorUtilities.CreateInstance<TImpl>(sp, parameters2)!;
+                    },
+                    existingService.Lifetime);
+            }
+            else
+            {
+                throw new Exception(
+                    "Unable to instantiate decorated type.");
+            }
 
-        services.Remove(existingService);
-        services.Add(newService);
+            services.Remove(existingService);
+            services.Add(newService);
+        }
 
         return true;
     }


Test patch:
diff --git a/test/Finbuckle.MultiTenant.Test/DependencyInjection/ServiceCollectionExtensionsShould.cs b/test/Finbuckle.MultiTenant.Test/DependencyInjection/ServiceCollectionExtensionsShould.cs
index 18fd2b3b..82db0420 100644
--- a/test/Finbuckle.MultiTenant.Test/DependencyInjection/ServiceCollectionExtensionsShould.cs
+++ b/test/Finbuckle.MultiTenant.Test/DependencyInjection/ServiceCollectionExtensionsShould.cs
@@ -125,4 +125,78 @@ public void RegisterAllOptionsPerTenant()
             Assert.Single(config);
             Assert.Null(config.Select(c => (ConfigureNamedOptions<TestOptions, IMultiTenantContextAccessor<TenantInfo>>)c).Single().Name);
         }
+        
+    [Fact]
+    public void DecorateService_WithImplementationType_WrapsService()
+    {
+        var services = new ServiceCollection();
+        services.AddSingleton<ITestService, TestService>();
+        bool result = services.DecorateService<ITestService, DecoratedTestService>();
+        Assert.True(result);
+        var provider = services.BuildServiceProvider();
+        var service = provider.GetRequiredService<ITestService>();
+        Assert.IsType<DecoratedTestService>(service);
+        Assert.IsType<TestService>(((DecoratedTestService)service).Inner);
+    }
+
+    [Fact]
+    public void DecorateService_WithImplementationInstance_WrapsService()
+    {
+        var instance = new TestService();
+        var services = new ServiceCollection();
+        services.AddSingleton<ITestService>(instance);
+        bool result = services.DecorateService<ITestService, DecoratedTestService>();
+        Assert.True(result);
+        var provider = services.BuildServiceProvider();
+        var service = provider.GetRequiredService<ITestService>();
+        Assert.IsType<DecoratedTestService>(service);
+        Assert.Same(instance, ((DecoratedTestService)service).Inner);
+    }
+
+    [Fact]
+    public void DecorateService_WithImplementationFactory_WrapsService()
+    {
+        var services = new ServiceCollection();
+        services.AddSingleton<ITestService>(sp => new TestService());
+        bool result = services.DecorateService<ITestService, DecoratedTestService>();
+        Assert.True(result);
+        var provider = services.BuildServiceProvider();
+        var service = provider.GetRequiredService<ITestService>();
+        Assert.IsType<DecoratedTestService>(service);
+        Assert.IsType<TestService>(((DecoratedTestService)service).Inner);
+    }
+
+    [Fact]
+    public void DecorateService_ThrowsIfNoServiceFound()
+    {
+        var services = new ServiceCollection();
+        Assert.Throws<ArgumentException>(() => services.DecorateService<ITestService, DecoratedTestService>());
+    }
+
+    [Fact]
+    public void DecorateService_WrapsAllServicesOfSameType()
+    {
+        var services = new ServiceCollection();
+        var instance1 = new TestService();
+        var instance2 = new TestService();
+        services.AddSingleton<ITestService>(instance1);
+        services.AddSingleton<ITestService>(instance2);
+        bool result = services.DecorateService<ITestService, DecoratedTestService>();
+        Assert.True(result);
+        var provider = services.BuildServiceProvider();
+        var all = provider.GetServices<ITestService>().ToList();
+        Assert.Equal(2, all.Count);
+        Assert.All(all, s => Assert.IsType<DecoratedTestService>(s));
+        var inners = all.Cast<DecoratedTestService>().Select(d => d.Inner).ToList();
+        Assert.Contains(instance1, inners);
+        Assert.Contains(instance2, inners);
+    }
+
+    public interface ITestService { }
+    public class TestService : ITestService { }
+    public class DecoratedTestService : ITestService
+    {
+        public ITestService Inner { get; }
+        public DecoratedTestService(ITestService inner) => Inner = inner;
+    }
 }
\ No newline at end of file