I run into a runtime error when using autofac in combination with hybrid cache and redis. I hope someone can explain why this error occurs.
When using autofac to resolve an instance of HybridCache in combination with redis, autofac gives a DependencyResolutionException that DefaultHybridCache is trying to create another instance of itself. This happens when trying to resolve an instance of HybridcCache.
Full error:
DependencyResolutionException: The constructor of type 'Microsoft.Extensions.Caching.Hybrid.Internal.DefaultHybridCache' attempted to create another instance of itself. This is not permitted because the service is configured to only allowed a single instance per lifetime scope.
This happens when I register redis with a shared multiplexer. I'm not sure why this matters, see below for reproduction code. There is no error when using the default DI system of .NET.
So short question is, is this a bug in autofac (or the redis/hybridcache library) or am I using the libraries wrong?
Minimal reproduction is as follows: Adding autofac to a blazor server application set up as below gives the exception when resolving HyrbidCache.
using Autofac.Extensions.DependencyInjection;
using BlazorAppAutofac.Components;
using BlazorAppAutofac.Services;
using StackExchange.Redis;
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
// Add services to the container.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
builder.Services.AddHybridCache();
IConnectionMultiplexer connectionMultiplexer = ConnectionMultiplexer.Connect(new ConfigurationOptions
{
EndPoints = { $"localhost:6379" },
AbortOnConnectFail = false,
});
builder.Services.AddSingleton<IConnectionMultiplexer>(connectionMultiplexer);
builder.Services.AddStackExchangeRedisCache(options =>
{
options.ConnectionMultiplexerFactory = () => Task.FromResult(connectionMultiplexer);
options.InstanceName = "BlazorAppAutofac";
});
builder.Services.AddTransient<IMessageService, MessageService>();
When using the below to register redis (so not adding the multiplexer) does not give an exception.
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = builder.Configuration.GetConnectionString("localhost:6379");
options.InstanceName = "BlazorAppAutofac";
});
In Home.razor
@inject IMessageService MessageService
In MessageService.cs
public class MessageService : IMessageService
{
private readonly HybridCache _cache;
public MessageService(HybridCache cache)
{
_cache = cache;
}
}
I would have expected both options for registration to work as they also work with the default dependency injection.
Interestingly enough in the code of StackExchangeRedis there seems to be some code that tries to circumvent the problem.
internal sealed class RedisCacheImpl : RedisCache
{
private readonly IServiceProvider _services;
internal override bool IsHybridCacheActive()
=> _services.GetService<HybridCache>() is not null;
public RedisCacheImpl(IOptions<RedisCacheOptions> optionsAccessor, ILogger<RedisCache> logger, IServiceProvider services)
: base(optionsAccessor, logger)
{
_services = services; // important: do not check for HybridCache here due to dependency - creates a cycle
}
public RedisCacheImpl(IOptions<RedisCacheOptions> optionsAccessor, IServiceProvider services)
: base(optionsAccessor)
{
_services = services; // important: do not check for HybridCache here due to dependency - creates a cycle
}
}
Stacktrace:
DependencyResolutionException: The constructor of type 'Microsoft.Extensions.Caching.Hybrid.Internal.DefaultHybridCache' attempted to create another instance of itself. This is not permitted because the service is configured to only allowed a single instance per lifetime scope.
Autofac.Core.Lifetime.LifetimeScope.CreateSharedInstance(Guid id, Func<object> creator)
Autofac.Core.Lifetime.LifetimeScope.CreateSharedInstance(Guid primaryId, Nullable<Guid> qualifyingId, Func<object> creator)
Autofac.Core.Resolving.Middleware.SharingMiddleware.Execute(ResolveRequestContext context, Action<ResolveRequestContext> next)
Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder+<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext context)
Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder+<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext context)
Autofac.Core.Resolving.Middleware.CircularDependencyDetectorMiddleware.Execute(ResolveRequestContext context, Action<ResolveRequestContext> next)
Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder+<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext context)
Autofac.Core.Pipeline.ResolvePipeline.Invoke(ResolveRequestContext context)
Autofac.Core.Resolving.ResolveOperation.InvokePipeline(ref ResolveRequest request, DefaultResolveRequestContext requestContext)
Autofac.Core.Resolving.ResolveOperation.GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, ref ResolveRequest request)
Autofac.Core.Resolving.ResolveOperation.Autofac.Core.Resolving.IResolveOperation.GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, ref ResolveRequest request)
Autofac.Core.Resolving.Pipeline.DefaultResolveRequestContext.ResolveComponent(ref ResolveRequest request)
Autofac.Core.Activators.Reflection.AutowiringParameter+<>c__DisplayClass0_0.<CanSupplyValue>b__0()
Autofac.Core.Activators.Reflection.BoundConstructor.Instantiate()
Autofac.Core.Activators.Reflection.ReflectionActivator+<>c__DisplayClass14_0.<UseSingleConstructorActivation>b__0(ResolveRequestContext context, Action<ResolveRequestContext> next)
Autofac.Core.Resolving.Middleware.DelegateMiddleware.Execute(ResolveRequestContext context, Action<ResolveRequestContext> next)
Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder+<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext context)
Autofac.Core.Resolving.Middleware.DisposalTrackingMiddleware.Execute(ResolveRequestContext context, Action<ResolveRequestContext> next)
Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder+<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext context)
Autofac.Extensions.DependencyInjection.KeyedServiceMiddleware.Execute(ResolveRequestContext context, Action<ResolveRequestContext> next)
Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder+<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext context)
Autofac.Core.Resolving.Middleware.ActivatorErrorHandlingMiddleware.Execute(ResolveRequestContext context, Action<ResolveRequestContext> next)