ASP.NET 核心 2.x 中间件的自定义依赖注入解析器?

Custom Dependency Injection Resolver for ASP.NET Core 2.x Middleware?

给定一个 ASP.NET 核心 2.x 应用程序,假设我正在使用两种分布式缓存机制:

services.AddDistributedSqlServerCache()
services.AddDistributedRedisCache()

据我了解,由于 Redis 是最后注册的,因此只要请求 IDistributedCache 的实例,它就会解析为 RedisCache 实现。

在我的项目中,我还使用了 Distributed-Cache 标签助手,我想将其解析为 RedisCache(没问题,适用于上述设置)。

不过,我也在使用 Session 中间件,它也需要 IDistributedCache 实现。

我需要 Session 中间件来解析 SQL 分布式缓存和 Distributed-Cache 标签助手以及 IDistributedCache 缓存的任何其他请求来解析RedisCache.

如果我对 this 文章的理解正确,您可以指定服务定位器为对 services.AddSingleton 的一般调用解析到哪个实现,但这似乎并没有翻译像 AddSession().

有什么解决办法吗?

AddDistributedSqlServerCache()AddDistributedRedisCache()都为IDistributedCache注册了一个单例:SqlServerCacheRedisCache分别。由于依赖组件只依赖于 IDistributedCache,它们都将获得相同的分布式缓存实现(取决于最后注册的内容)。

这通常是设计使然,因为实现,例如会话中间件,不应该关心 IDistributedCache 的实际注册实现是什么。它只取决于有 some 并使用它。同样,其他服务也将只使用一个分布式缓存依赖项。

通常,真的没有办法解决这个问题。您最终可以做的是创建一些实现 IDistributedCache 本身的适配器,然后根据传递的参数委托给 SQL 服务器缓存或 Redis 缓存。

对于您的情况,还有一种更简单的方法。由于 ASP.NET Core 的构建具有很强的可扩展性,并且大多数组件都可以被其他实现简单地交换,我们可以在这里利用它来使会话中间件只使用专门的分布式缓存,而其他一切都回退到默认缓存.

为此,我们只需实施 ISessionStore 并注册它,这基本上也是 AddSession() 所做的。在自定义会话存储实现中,我们将直接依赖 SqlServerCache 而不是依赖于 IDistributedCache。这样一来,我们就不会退回到默认的 IDistributedCache(无论那是什么),而是强制系统使用 SqlServerCache.

public class SqlServerCacheSessionStore : ISessionStore
{
    private readonly IDistributedCache _cache;
    private readonly ILoggerFactory _loggerFactory;

    public SqlServerCacheSessionStore(SqlServerCache cache, ILoggerFactory loggerFactory)
    {
        _cache = cache ?? throw new ArgumentNullException(nameof(cache));
        _loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
    }

    public ISession Create(string sessionKey, TimeSpan idleTimeout, TimeSpan ioTimeout, Func<bool> tryEstablishSession, bool isNewSessionKey)
    {
        if (string.IsNullOrEmpty(sessionKey))
            throw new ArgumentNullException(nameof(sessionKey));
        if (tryEstablishSession == null)
            throw new ArgumentNullException(nameof(tryEstablishSession));

        return new DistributedSession(_cache, sessionKey, idleTimeout, ioTimeout, tryEstablishSession, _loggerFactory, isNewSessionKey);
    }
}

这实际上与 DistributedSessionStore 相同的实现,后者是默认的 ISessionStore 实现,除了我们依赖 SqlServerCache 而不是 IDistributedCache.

现在,我们只需要在 Configure 方法中连接所有内容:

// we keep the Redis cache as the default
services.AddDistributedRedisCache();

// no call to `AddSqlServerCache` as we don’t want to overwrite the `IDistributedCache`
// registration; instead, register (and configure) the SqlServerCache directly
services.AddSingleton<SqlServerCache>();
services.Configure<SqlServerCacheOptions>(options =>
{
    // here goes the configuration that would normally be done in the
    // configure action passed to `AddSqlServerCache`
    options.ConnectionString = Configuration.GetConnectionString("DistributedCache");
});

// add session, but overwrite the `ISessionStore` afterwards
services.AddSession();
services.AddTransient<ISessionStore, SqlServerCacheSessionStore>();

这应该就是全部了。因此,当会话中间件现在解析 ISessionStore 时,它将获得直接依赖于 SqlServerCacheSqlServerCacheSessionStore 而不是一般的 IDistributedCache ,后者将成为 Redis 缓存。