diff --git a/src/Libraries/Microsoft.Extensions.Caching.Hybrid/HybridCacheOptions.cs b/src/Libraries/Microsoft.Extensions.Caching.Hybrid/HybridCacheOptions.cs
index 7b87755da7b..d55ac1a4ea1 100644
--- a/src/Libraries/Microsoft.Extensions.Caching.Hybrid/HybridCacheOptions.cs
+++ b/src/Libraries/Microsoft.Extensions.Caching.Hybrid/HybridCacheOptions.cs
@@ -57,4 +57,9 @@ public class HybridCacheOptions
/// should not be visible in metrics systems.
///
public bool ReportTagMetrics { get; set; }
+
+ ///
+ /// Gets or sets the key used to resolve the distributed cache service from the .
+ ///
+ public object? DistributedCacheServiceKey { get; set; }
}
diff --git a/src/Libraries/Microsoft.Extensions.Caching.Hybrid/HybridCacheServiceExtensions.cs b/src/Libraries/Microsoft.Extensions.Caching.Hybrid/HybridCacheServiceExtensions.cs
index d28dc4e47d5..060307026d6 100644
--- a/src/Libraries/Microsoft.Extensions.Caching.Hybrid/HybridCacheServiceExtensions.cs
+++ b/src/Libraries/Microsoft.Extensions.Caching.Hybrid/HybridCacheServiceExtensions.cs
@@ -5,6 +5,7 @@
using Microsoft.Extensions.Caching.Hybrid;
using Microsoft.Extensions.Caching.Hybrid.Internal;
using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Options;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Extensions.DependencyInjection;
@@ -17,28 +18,111 @@ public static class HybridCacheServiceExtensions
///
/// Adds support for multi-tier caching services.
///
- /// A builder instance that allows further configuration of the system.
+ /// The to add the service to.
+ /// A delegate to run to configure the instance.
+ /// A builder instance that allows further configuration of the service.
public static IHybridCacheBuilder AddHybridCache(this IServiceCollection services, Action setupAction)
{
_ = Throw.IfNull(setupAction);
- _ = AddHybridCache(services);
+
+ var builder = AddHybridCache(services);
_ = services.Configure(setupAction);
- return new HybridCacheBuilder(services);
+
+ return builder;
}
///
/// Adds support for multi-tier caching services.
///
- /// A builder instance that allows further configuration of the system.
+ /// The to add the service to.
+ /// A builder instance that allows further configuration of the service.
public static IHybridCacheBuilder AddHybridCache(this IServiceCollection services)
{
_ = Throw.IfNull(services);
+ var builder = PrepareServices(services);
+
+ services.TryAddSingleton();
+
+ return builder;
+ }
+
+ ///
+ /// Adds support for multi-tier caching services with a keyed registration.
+ ///
+ /// The to add the service to.
+ /// The key for the service registration.
+ /// A delegate to run to configure the instance.
+ /// A builder instance that allows further configuration of the service.
+ public static IHybridCacheBuilder AddKeyedHybridCache(this IServiceCollection services, object? serviceKey, Action setupAction) =>
+ AddKeyedHybridCache(services, serviceKey, serviceKey?.ToString() ?? Options.Options.DefaultName, setupAction);
+
+ ///
+ /// Adds support for multi-tier caching services with a keyed registration.
+ ///
+ /// The to add the service to.
+ /// The key for the service registration.
+ /// The named options name to use for the instance.
+ /// A delegate to run to configure the instance.
+ /// A builder instance that allows further configuration of the service.
+ public static IHybridCacheBuilder AddKeyedHybridCache(this IServiceCollection services, object? serviceKey, string optionsName, Action setupAction)
+ {
+ _ = Throw.IfNull(setupAction);
+
+ var builder = AddKeyedHybridCache(services, serviceKey, optionsName);
+ _ = services.AddOptions(optionsName).Configure(setupAction);
+
+ return builder;
+ }
+
+ ///
+ /// Adds support for multi-tier caching services with a keyed registration.
+ ///
+ /// The to add the service to.
+ /// The key for the service registration.
+ /// A builder instance that allows further configuration of the service.
+ public static IHybridCacheBuilder AddKeyedHybridCache(this IServiceCollection services, object? serviceKey) =>
+ AddKeyedHybridCache(services, serviceKey, serviceKey?.ToString() ?? Options.Options.DefaultName);
+
+ ///
+ /// Adds support for multi-tier caching services with a keyed registration.
+ ///
+ /// The to add the service to.
+ /// The key for the service registration.
+ /// The named options name to use for the instance.
+ /// A builder instance that allows further configuration of the service.
+ public static IHybridCacheBuilder AddKeyedHybridCache(this IServiceCollection services, object? serviceKey, string optionsName)
+ {
+ _ = Throw.IfNull(optionsName);
+
+ var builder = PrepareServices(services);
+ _ = services.AddOptions(optionsName);
+
+ _ = services.AddKeyedSingleton(serviceKey, (sp, key) =>
+ {
+ var optionsService = sp.GetRequiredService>();
+ var options = optionsService.Get(optionsName);
+
+ return new DefaultHybridCache(options, sp);
+ });
+
+ return builder;
+ }
+
+ ///
+ /// Adds the services required for hybrid caching.
+ ///
+ /// The to prepare with prerequisites.
+ /// A builder instance that allows further configuration of the service.
+ private static HybridCacheBuilder PrepareServices(IServiceCollection services)
+ {
+ _ = Throw.IfNull(services);
+
services.TryAddSingleton(TimeProvider.System);
_ = services.AddOptions().AddMemoryCache();
services.TryAddSingleton();
services.TryAddSingleton>(InbuiltTypeSerializer.Instance);
services.TryAddSingleton>(InbuiltTypeSerializer.Instance);
- services.TryAddSingleton();
+
return new HybridCacheBuilder(services);
}
}
diff --git a/src/Libraries/Microsoft.Extensions.Caching.Hybrid/Internal/DefaultHybridCache.cs b/src/Libraries/Microsoft.Extensions.Caching.Hybrid/Internal/DefaultHybridCache.cs
index 84de2fe52e8..93e1e5457cb 100644
--- a/src/Libraries/Microsoft.Extensions.Caching.Hybrid/Internal/DefaultHybridCache.cs
+++ b/src/Libraries/Microsoft.Extensions.Caching.Hybrid/Internal/DefaultHybridCache.cs
@@ -65,13 +65,23 @@ internal enum CacheFeatures
internal bool HasBackendCache => (_features & CacheFeatures.BackendCache) != 0;
public DefaultHybridCache(IOptions options, IServiceProvider services)
+ : this(Throw.IfNull(options).Value, services)
+ {
+ }
+
+ public DefaultHybridCache(HybridCacheOptions options, IServiceProvider services)
{
_services = Throw.IfNull(services);
_localCache = services.GetRequiredService();
- _options = options.Value;
+ _options = options;
_logger = services.GetService()?.CreateLogger(typeof(HybridCache)) ?? NullLogger.Instance;
_clock = services.GetService() ?? TimeProvider.System;
- _backendCache = services.GetService(); // note optional
+
+ // The backend cache service is optional; if not provided, we operate as a pure L1 cache.
+ // If a service key is provided, the service must be present.
+ _backendCache = _options.DistributedCacheServiceKey is null
+ ? services.GetService()
+ : services.GetRequiredKeyedService(_options.DistributedCacheServiceKey);
// ignore L2 if it is really just the same L1, wrapped
// (note not just an "is" test; if someone has a custom subclass, who knows what it does?)
diff --git a/src/Libraries/Microsoft.Extensions.Caching.Hybrid/Microsoft.Extensions.Caching.Hybrid.json b/src/Libraries/Microsoft.Extensions.Caching.Hybrid/Microsoft.Extensions.Caching.Hybrid.json
index 2c1a811b223..10be31168ba 100644
Binary files a/src/Libraries/Microsoft.Extensions.Caching.Hybrid/Microsoft.Extensions.Caching.Hybrid.json and b/src/Libraries/Microsoft.Extensions.Caching.Hybrid/Microsoft.Extensions.Caching.Hybrid.json differ
diff --git a/test/Libraries/Microsoft.Extensions.Caching.Hybrid.Tests/BasicConfig.json b/test/Libraries/Microsoft.Extensions.Caching.Hybrid.Tests/BasicConfig.json
deleted file mode 100644
index 374114fb1db..00000000000
--- a/test/Libraries/Microsoft.Extensions.Caching.Hybrid.Tests/BasicConfig.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "no_entry_options": {
- "MaximumKeyLength": 937
- },
- "with_entry_options": {
- "MaximumKeyLength": 937,
- "DefaultEntryOptions": {
- "LocalCacheExpiration": "00:02:00",
- "Flags": "DisableCompression,DisableLocalCacheRead"
- }
- }
-}
diff --git a/test/Libraries/Microsoft.Extensions.Caching.Hybrid.Tests/Microsoft.Extensions.Caching.Hybrid.Tests.csproj b/test/Libraries/Microsoft.Extensions.Caching.Hybrid.Tests/Microsoft.Extensions.Caching.Hybrid.Tests.csproj
index fb8863cf776..3cd6a56dca5 100644
--- a/test/Libraries/Microsoft.Extensions.Caching.Hybrid.Tests/Microsoft.Extensions.Caching.Hybrid.Tests.csproj
+++ b/test/Libraries/Microsoft.Extensions.Caching.Hybrid.Tests/Microsoft.Extensions.Caching.Hybrid.Tests.csproj
@@ -23,10 +23,4 @@
-
-
- PreserveNewest
-
-
-
diff --git a/test/Libraries/Microsoft.Extensions.Caching.Hybrid.Tests/ServiceConstructionTests.cs b/test/Libraries/Microsoft.Extensions.Caching.Hybrid.Tests/ServiceConstructionTests.cs
index d66b325e802..6aabe04f693 100644
--- a/test/Libraries/Microsoft.Extensions.Caching.Hybrid.Tests/ServiceConstructionTests.cs
+++ b/test/Libraries/Microsoft.Extensions.Caching.Hybrid.Tests/ServiceConstructionTests.cs
@@ -6,13 +6,15 @@
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.Hybrid.Internal;
using Microsoft.Extensions.Caching.Memory;
+using Microsoft.Extensions.Caching.SqlServer;
+using Microsoft.Extensions.Caching.StackExchangeRedis;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
#if NET9_0_OR_GREATER
using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Configuration.Json;
#endif
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
@@ -51,12 +53,228 @@ public void CanCreateServiceWithManualOptions()
Assert.Null(defaults.LocalCacheExpiration); // wasn't specified
}
+ [Fact]
+ public void CanCreateServiceWithKeyedDistributedCache()
+ {
+ var services = new ServiceCollection();
+ services.TryAddKeyedSingleton(typeof(CustomMemoryDistributedCache1));
+ services.AddHybridCache(options => options.DistributedCacheServiceKey = typeof(CustomMemoryDistributedCache1));
+
+ using ServiceProvider provider = services.BuildServiceProvider();
+ var hybrid = Assert.IsType(provider.GetRequiredService());
+ var hybridOptions = hybrid.Options;
+
+ var backend = Assert.IsType(hybrid.BackendCache);
+ Assert.Same(typeof(CustomMemoryDistributedCache1), hybridOptions.DistributedCacheServiceKey);
+ Assert.Same(backend, provider.GetRequiredKeyedService(typeof(CustomMemoryDistributedCache1)));
+ }
+
+ [Fact]
+ public void ThrowsWhenDistributedCacheKeyNotRegistered()
+ {
+ var services = new ServiceCollection();
+ services.AddHybridCache(options => options.DistributedCacheServiceKey = typeof(CustomMemoryDistributedCache1));
+ using ServiceProvider provider = services.BuildServiceProvider();
+
+ Assert.Throws(provider.GetRequiredService);
+ }
+
+ [Fact]
+ public void ThrowsWhenRegisteredDistributedCacheIsNotKeyed()
+ {
+ var services = new ServiceCollection();
+ services.AddDistributedMemoryCache();
+ services.AddHybridCache(options => options.DistributedCacheServiceKey = typeof(CustomMemoryDistributedCache1));
+ using ServiceProvider provider = services.BuildServiceProvider();
+
+ Assert.Throws(provider.GetRequiredService);
+ }
+
+ [Fact]
+ public void CanCreateKeyedHybridCacheServiceWithNullKey()
+ {
+ var services = new ServiceCollection();
+ services.AddKeyedHybridCache(null);
+ using ServiceProvider provider = services.BuildServiceProvider();
+
+ // Resolves using null key registration
+ Assert.IsType(provider.GetRequiredKeyedService(null));
+
+ // Resolves as the non-keyed registration
+ Assert.IsType(provider.GetRequiredService());
+ }
+
+ [Fact]
+ public void CanCreateKeyedServicesWithStringKeys()
+ {
+ var services = new ServiceCollection();
+ services.AddKeyedHybridCache("one");
+ services.AddKeyedHybridCache("two");
+ services.AddHybridCache();
+ using ServiceProvider provider = services.BuildServiceProvider();
+
+ Assert.IsType(provider.GetRequiredKeyedService("one"));
+ Assert.IsType(provider.GetRequiredKeyedService("two"));
+ Assert.IsType(provider.GetRequiredKeyedService(null));
+ Assert.IsType(provider.GetRequiredService());
+ }
+
+ [Fact]
+ public void CanCreateKeyedServicesWithStringKeysAndSetupActions()
+ {
+ var services = new ServiceCollection();
+ services.AddKeyedHybridCache("one", options => options.MaximumKeyLength = 1);
+ services.AddKeyedHybridCache("two", options => options.MaximumKeyLength = 2);
+ services.AddKeyedHybridCache(null, options => options.MaximumKeyLength = 3);
+ using ServiceProvider provider = services.BuildServiceProvider();
+
+ var one = Assert.IsType(provider.GetRequiredKeyedService("one"));
+ Assert.Equal(1, one.Options.MaximumKeyLength);
+
+ var two = Assert.IsType(provider.GetRequiredKeyedService("two"));
+ Assert.Equal(2, two.Options.MaximumKeyLength);
+
+ var threeKeyed = Assert.IsType(provider.GetRequiredKeyedService(null));
+ Assert.Equal(3, threeKeyed.Options.MaximumKeyLength);
+
+ var threeUnkeyed = Assert.IsType(provider.GetRequiredService());
+ Assert.Equal(3, threeUnkeyed.Options.MaximumKeyLength);
+ }
+
+ [Fact]
+ public void CanCreateKeyedServicesWithTypeKeys()
+ {
+ var services = new ServiceCollection();
+ services.AddKeyedHybridCache(typeof(string));
+ services.AddKeyedHybridCache(typeof(int));
+ services.AddHybridCache();
+ using ServiceProvider provider = services.BuildServiceProvider();
+
+ Assert.IsType(provider.GetRequiredKeyedService(typeof(string)));
+ Assert.IsType(provider.GetRequiredKeyedService(typeof(int)));
+ Assert.IsType(provider.GetRequiredKeyedService(null));
+ Assert.IsType(provider.GetRequiredService());
+ }
+
+ [Fact]
+ public void CanCreateKeyedServicesWithTypeKeysAndSetupActions()
+ {
+ var services = new ServiceCollection();
+ services.AddKeyedHybridCache(typeof(string), options => options.MaximumKeyLength = 1);
+ services.AddKeyedHybridCache(typeof(int), options => options.MaximumKeyLength = 2);
+ services.AddKeyedHybridCache(null, options => options.MaximumKeyLength = 3);
+
+ using ServiceProvider provider = services.BuildServiceProvider();
+ var one = Assert.IsType(provider.GetRequiredKeyedService(typeof(string)));
+ Assert.Equal(1, one.Options.MaximumKeyLength);
+
+ var two = Assert.IsType(provider.GetRequiredKeyedService(typeof(int)));
+ Assert.Equal(2, two.Options.MaximumKeyLength);
+
+ var threeKeyed = Assert.IsType(provider.GetRequiredKeyedService(null));
+ Assert.Equal(3, threeKeyed.Options.MaximumKeyLength);
+
+ var threeUnkeyed = Assert.IsType(provider.GetRequiredService());
+ Assert.Equal(3, threeUnkeyed.Options.MaximumKeyLength);
+ }
+
+ [Fact]
+ public void CanCreateKeyedServicesWithKeyedDistributedCaches()
+ {
+ var services = new ServiceCollection();
+ services.TryAddKeyedSingleton(typeof(CustomMemoryDistributedCache1));
+ services.TryAddKeyedSingleton(typeof(CustomMemoryDistributedCache2));
+
+ services.AddKeyedHybridCache("one", options => options.DistributedCacheServiceKey = typeof(CustomMemoryDistributedCache1));
+ services.AddKeyedHybridCache("two", options => options.DistributedCacheServiceKey = typeof(CustomMemoryDistributedCache2));
+ using ServiceProvider provider = services.BuildServiceProvider();
+
+ var cacheOne = Assert.IsType(provider.GetRequiredKeyedService("one"));
+ var cacheOneOptions = cacheOne.Options;
+ var cacheOneBackend = Assert.IsType(cacheOne.BackendCache);
+ Assert.Same(typeof(CustomMemoryDistributedCache1), cacheOneOptions.DistributedCacheServiceKey);
+ Assert.Same(cacheOneBackend, provider.GetRequiredKeyedService(typeof(CustomMemoryDistributedCache1)));
+
+ var cacheTwo = Assert.IsType(provider.GetRequiredKeyedService("two"));
+ var cacheTwoOptions = cacheTwo.Options;
+ var cacheTwoBackend = Assert.IsType(cacheTwo.BackendCache);
+ Assert.Same(typeof(CustomMemoryDistributedCache2), cacheTwoOptions.DistributedCacheServiceKey);
+ Assert.Same(cacheTwoBackend, provider.GetRequiredKeyedService(typeof(CustomMemoryDistributedCache2)));
+ }
+
+ [Fact]
+ public async Task KeyedHybridCaches_ShareLocalMemoryCache()
+ {
+ var services = new ServiceCollection();
+ services.AddMemoryCache(options => options.SizeLimit = 2);
+ services.AddSingleton();
+ services.AddKeyedHybridCache("hybrid1");
+ services.AddKeyedHybridCache("hybrid2");
+ services.AddKeyedHybridCache("hybrid3");
+
+ using ServiceProvider provider = services.BuildServiceProvider();
+ var hybrid1 = provider.GetRequiredKeyedService("hybrid1");
+ var hybrid2 = provider.GetRequiredKeyedService("hybrid2");
+ var hybrid3 = provider.GetRequiredKeyedService("hybrid3");
+
+ await hybrid1.SetAsync("entry1", 1);
+ await hybrid2.SetAsync("entry2", 2);
+ await hybrid3.SetAsync("entry3", 3);
+
+ var localCache = provider.GetRequiredService();
+ Assert.True(localCache.TryGetValue("entry1", out object? _));
+ Assert.True(localCache.TryGetValue("entry2", out object? _));
+
+ // The third item fails to be cached locally because of the shared local cache size limit
+ Assert.False(localCache.TryGetValue("entry3", out object? _));
+
+ // But we can still get it from the hybrid cache (which gets it from the distributed cache)
+ var actual3 = await hybrid3.GetOrCreateAsync("entry3", ct =>
+ {
+ Assert.Fail("Should not be called as the item should be found in the distributed cache");
+ return new ValueTask(-1);
+ });
+
+ Assert.Equal(3, actual3);
+ }
+
+ [Fact]
+ public void CanCreateRedisAndSqlServerBackedHybridCaches()
+ {
+ var services = new ServiceCollection();
+ services.AddKeyedSingleton("Redis");
+
+ services.AddKeyedSingleton("SqlServer",
+ (sp, key) => new SqlServerCache(new SqlServerCacheOptions
+ {
+ ConnectionString = "test",
+ SchemaName = "test",
+ TableName = "test"
+ }));
+
+ services.AddKeyedHybridCache("HybridWithRedis", options => options.DistributedCacheServiceKey = "Redis");
+ services.AddKeyedHybridCache("HybridWithSqlServer", options => options.DistributedCacheServiceKey = "SqlServer");
+
+ using ServiceProvider provider = services.BuildServiceProvider();
+ var hybridWithRedis = Assert.IsType(provider.GetRequiredKeyedService("HybridWithRedis"));
+ var hybridWithRedisBackend = Assert.IsType(hybridWithRedis.BackendCache);
+ Assert.Same(hybridWithRedisBackend, provider.GetRequiredKeyedService("Redis"));
+
+ var hybridWithSqlServer = Assert.IsType(provider.GetRequiredKeyedService("HybridWithSqlServer"));
+ var hybridWithSqlServerBackend = Assert.IsType(hybridWithSqlServer.BackendCache);
+ Assert.Same(hybridWithSqlServerBackend, provider.GetRequiredKeyedService("SqlServer"));
+ }
+
#if NET9_0_OR_GREATER // for Bind API
[Fact]
public void CanParseOptions_NoEntryOptions()
{
- var source = new JsonConfigurationSource { Path = "BasicConfig.json" };
- var configBuilder = new ConfigurationBuilder { Sources = { source } };
+ var configBuilder = new ConfigurationBuilder();
+
+ configBuilder.AddInMemoryCollection([
+ new("no_entry_options:MaximumKeyLength", "937")
+ ]);
+
var config = configBuilder.Build();
var options = new HybridCacheOptions();
ConfigurationBinder.Bind(config, "no_entry_options", options);
@@ -68,8 +286,14 @@ public void CanParseOptions_NoEntryOptions()
[Fact]
public void CanParseOptions_WithEntryOptions() // in particular, check we can parse the timespan and [Flags] enums
{
- var source = new JsonConfigurationSource { Path = "BasicConfig.json" };
- var configBuilder = new ConfigurationBuilder { Sources = { source } };
+ var configBuilder = new ConfigurationBuilder();
+
+ configBuilder.AddInMemoryCollection([
+ new("with_entry_options:MaximumKeyLength", "937"),
+ new("with_entry_options:DefaultEntryOptions:Flags", "DisableCompression, DisableLocalCacheRead"),
+ new("with_entry_options:DefaultEntryOptions:LocalCacheExpiration", "00:02:00")
+ ]);
+
var config = configBuilder.Build();
var options = new HybridCacheOptions();
ConfigurationBinder.Bind(config, "with_entry_options", options);
@@ -81,6 +305,122 @@ public void CanParseOptions_WithEntryOptions() // in particular, check we can pa
Assert.Equal(TimeSpan.FromSeconds(120), defaults.LocalCacheExpiration);
Assert.Null(defaults.Expiration); // wasn't specified
}
+
+ [Fact]
+ public void CanCreateKeyedServicesWithKeyedDistributedCaches_UsingNamedOptions()
+ {
+ var configBuilder = new ConfigurationBuilder();
+
+ configBuilder.AddInMemoryCollection([
+ new("HybridOne:DistributedCacheServiceKey", "DistributedOne"),
+ new("HybridTwo:DistributedCacheServiceKey", "DistributedTwo")
+ ]);
+
+ var config = configBuilder.Build();
+
+ var services = new ServiceCollection();
+ services.AddKeyedSingleton("DistributedOne");
+ services.AddKeyedSingleton("DistributedTwo");
+ services.AddOptions("HybridOne").Configure(options => ConfigurationBinder.Bind(config, "HybridOne", options));
+ services.AddOptions("HybridTwo").Configure(options => ConfigurationBinder.Bind(config, "HybridTwo", options));
+ services.AddKeyedHybridCache(typeof(CustomMemoryDistributedCache1), "HybridOne");
+ services.AddKeyedHybridCache(typeof(CustomMemoryDistributedCache2), "HybridTwo");
+
+ using ServiceProvider provider = services.BuildServiceProvider();
+ var hybridOne = Assert.IsType(provider.GetRequiredKeyedService(typeof(CustomMemoryDistributedCache1)));
+ var hybridOneOptions = hybridOne.Options;
+ var hybridOneBackend = Assert.IsType(hybridOne.BackendCache);
+ Assert.Equal("DistributedOne", hybridOneOptions.DistributedCacheServiceKey);
+
+ var hybridTwo = Assert.IsType(provider.GetRequiredKeyedService(typeof(CustomMemoryDistributedCache2)));
+ var hybridTwoOptions = hybridTwo.Options;
+ var hybridTwoBackend = Assert.IsType(hybridTwo.BackendCache);
+ Assert.Equal("DistributedTwo", hybridTwoOptions.DistributedCacheServiceKey);
+
+ provider.GetRequiredKeyedService(typeof(CustomMemoryDistributedCache1));
+ provider.GetRequiredKeyedService(typeof(CustomMemoryDistributedCache1));
+ provider.GetRequiredKeyedService(typeof(CustomMemoryDistributedCache1));
+
+ provider.GetRequiredKeyedService(typeof(CustomMemoryDistributedCache2));
+ provider.GetRequiredKeyedService(typeof(CustomMemoryDistributedCache2));
+ provider.GetRequiredKeyedService(typeof(CustomMemoryDistributedCache2));
+ }
+
+ [Fact]
+ public void CanCreateKeyedServicesWithKeyedDistributedCaches_UsingSetupActions()
+ {
+ var configBuilder = new ConfigurationBuilder();
+
+ configBuilder.AddInMemoryCollection([
+ new("HybridOne:DistributedCacheServiceKey", "DistributedOne"),
+ new("HybridTwo:DistributedCacheServiceKey", "DistributedTwo")
+ ]);
+
+ var config = configBuilder.Build();
+
+ var services = new ServiceCollection();
+ services.AddKeyedSingleton("DistributedOne");
+ services.AddKeyedSingleton("DistributedTwo");
+ services.AddKeyedHybridCache("HybridOne", options => ConfigurationBinder.Bind(config, "HybridOne", options));
+ services.AddKeyedHybridCache("HybridTwo", options => ConfigurationBinder.Bind(config, "HybridTwo", options));
+
+ using ServiceProvider provider = services.BuildServiceProvider();
+ var hybridOne = Assert.IsType(provider.GetRequiredKeyedService("HybridOne"));
+ var hybridOneOptions = hybridOne.Options;
+ var hybridOneBackend = Assert.IsType(hybridOne.BackendCache);
+ Assert.Equal("DistributedOne", hybridOneOptions.DistributedCacheServiceKey);
+
+ var hybridTwo = Assert.IsType(provider.GetRequiredKeyedService("HybridTwo"));
+ var hybridTwoOptions = hybridTwo.Options;
+ var hybridTwoBackend = Assert.IsType(hybridTwo.BackendCache);
+ Assert.Equal("DistributedTwo", hybridTwoOptions.DistributedCacheServiceKey);
+
+ provider.GetRequiredKeyedService("HybridOne");
+ provider.GetRequiredKeyedService("HybridOne");
+ provider.GetRequiredKeyedService("HybridOne");
+
+ provider.GetRequiredKeyedService("HybridTwo");
+ provider.GetRequiredKeyedService("HybridTwo");
+ provider.GetRequiredKeyedService("HybridTwo");
+ }
+
+ [Fact]
+ public void CanCreateKeyedServicesWithKeyedDistributedCaches_UsingNamedOptionsAndSetupActions()
+ {
+ var configBuilder = new ConfigurationBuilder();
+
+ configBuilder.AddInMemoryCollection([
+ new("HybridOne:DistributedCacheServiceKey", "DistributedOne"),
+ new("HybridTwo:DistributedCacheServiceKey", "DistributedTwo")
+ ]);
+
+ var config = configBuilder.Build();
+
+ var services = new ServiceCollection();
+ services.AddKeyedSingleton("DistributedOne");
+ services.AddKeyedSingleton("DistributedTwo");
+ services.AddKeyedHybridCache(typeof(CustomMemoryDistributedCache1), "HybridOne", options => ConfigurationBinder.Bind(config, "HybridOne", options));
+ services.AddKeyedHybridCache(typeof(CustomMemoryDistributedCache2), "HybridTwo", options => ConfigurationBinder.Bind(config, "HybridTwo", options));
+
+ using ServiceProvider provider = services.BuildServiceProvider();
+ var hybridOne = Assert.IsType(provider.GetRequiredKeyedService(typeof(CustomMemoryDistributedCache1)));
+ var hybridOneOptions = hybridOne.Options;
+ var hybridOneBackend = Assert.IsType(hybridOne.BackendCache);
+ Assert.Equal("DistributedOne", hybridOneOptions.DistributedCacheServiceKey);
+
+ var hybridTwo = Assert.IsType(provider.GetRequiredKeyedService(typeof(CustomMemoryDistributedCache2)));
+ var hybridTwoOptions = hybridTwo.Options;
+ var hybridTwoBackend = Assert.IsType(hybridTwo.BackendCache);
+ Assert.Equal("DistributedTwo", hybridTwoOptions.DistributedCacheServiceKey);
+
+ provider.GetRequiredKeyedService(typeof(CustomMemoryDistributedCache1));
+ provider.GetRequiredKeyedService(typeof(CustomMemoryDistributedCache1));
+ provider.GetRequiredKeyedService(typeof(CustomMemoryDistributedCache1));
+
+ provider.GetRequiredKeyedService(typeof(CustomMemoryDistributedCache2));
+ provider.GetRequiredKeyedService(typeof(CustomMemoryDistributedCache2));
+ provider.GetRequiredKeyedService(typeof(CustomMemoryDistributedCache2));
+ }
#endif
[Fact]
@@ -173,7 +513,7 @@ public void DefaultMemoryDistributedCacheIsIgnored(bool manual)
public void SubclassMemoryDistributedCacheIsNotIgnored()
{
var services = new ServiceCollection();
- services.AddSingleton();
+ services.AddSingleton();
services.AddHybridCache();
using ServiceProvider provider = services.BuildServiceProvider();
var cache = Assert.IsType(provider.GetRequiredService());
@@ -293,14 +633,27 @@ public CustomMemoryCache(IOptions options, ILoggerFactory lo
}
}
- internal class CustomMemoryDistributedCache : MemoryDistributedCache
+ internal class CustomMemoryDistributedCache1 : MemoryDistributedCache
+ {
+ public CustomMemoryDistributedCache1(IOptions options)
+ : base(options)
+ {
+ }
+
+ public CustomMemoryDistributedCache1(IOptions options, ILoggerFactory loggerFactory)
+ : base(options, loggerFactory)
+ {
+ }
+ }
+
+ internal class CustomMemoryDistributedCache2 : MemoryDistributedCache
{
- public CustomMemoryDistributedCache(IOptions options)
+ public CustomMemoryDistributedCache2(IOptions options)
: base(options)
{
}
- public CustomMemoryDistributedCache(IOptions options, ILoggerFactory loggerFactory)
+ public CustomMemoryDistributedCache2(IOptions options, ILoggerFactory loggerFactory)
: base(options, loggerFactory)
{
}