如何将Ninject配置为始终停用池引用

本文关键字:引用 Ninject 配置 | 更新日期: 2023-09-27 18:17:57

我们正在使用一个使用池对象的库(ServiceStack.RedisPooledRedisClientManager)。对象是为多个web请求创建和重用的。但是,每次使用后,应该调用Dispose来将对象释放回池中。

默认情况下,Ninject只在之前未被停用时才停用对象引用。

池实例化一个对象并将其标记为活动。然后Ninject运行激活管道。在请求(web请求)结束时,Ninject运行调用Dispose的停用管道(因此池将对象标记为非活动)。下一个请求:使用第一个池实例,池将其标记为活动。然而,在请求结束时,Ninject没有运行它的去激活管道,因为ActivationCache已经将这个实例标记为去激活(这在管道中)。

下面是我们在一个新的MVC项目中添加的一个简单示例来演示这个问题:
public interface IFooFactory
{
    IFooClient GetClient();
    void DisposeClient(FooClient client);
}
public class PooledFooClientFactory : IFooFactory
{
    private readonly List<FooClient> pool = new List<FooClient>();
    public IFooClient GetClient()
    {
        lock (pool)
        {
            var client = pool.SingleOrDefault(c => !c.Active);
            if (client == null)
            {
                client = new FooClient(pool.Count + 1);
                client.Factory = this;
                pool.Add(client);
            }
            client.Active = true;
            return client;
        }
    }
    public void DisposeClient(FooClient client)
    {
        client.Active = false;
    }
}
public interface IFooClient
{
    void Use();
}
public class FooClient : IFooClient, IDisposable
{
    internal IFooFactory Factory { get; set; }
    internal bool Active { get; set; }
    internal int Id { get; private set; }
    public FooClient(int id)
    {
        this.Id = id;
    }
    public void Dispose()
    {
        if (Factory != null)
        {
            Factory.DisposeClient(this);
        }
    }
    public void Use()
    {
        Console.WriteLine("Using...");
    }
}
public class HomeController : Controller
{
    private IFooClient foo;
    public HomeController(IFooClient foo)
    {
        this.foo = foo;
    }
    public ActionResult Index()
    {
        foo.Use();
        return View();
    }
    public ActionResult About()
    {
        return View();
    }
}
// In the Ninject configuration (NinjectWebCommon.cs)
private static void RegisterServices(IKernel kernel)
{
    kernel.Bind<IFooFactory>()
        .To<PooledFooClientFactory>()
        .InSingletonScope();
    kernel.Bind<IFooClient>()
        .ToMethod(ctx => ctx.Kernel.Get<IFooFactory>().GetClient())
        .InRequestScope();
}

到目前为止,我们提出的解决方案是:

  1. 将这些对象标记为InTransientScope()并使用其他停用机制(如MVC ActionFilter在每次请求后处置对象)。我们将失去Ninject的停用过程的好处,并且需要一种间接的方法来处理对象。

  2. 编写一个自定义IActivationCache,检查池中对象是否处于活动状态。以下是我到目前为止所写的内容,但我希望其他人也能看到它的健壮性:

    public class PooledFooClientActivationCache : DisposableObject, IActivationCache, INinjectComponent, IDisposable, IPruneable
    {
        private readonly ActivationCache realCache;
        public PooledFooClientActivationCache(ICachePruner cachePruner)
        {
            realCache = new ActivationCache(cachePruner);
        }
        public void AddActivatedInstance(object instance)
        {
            realCache.AddActivatedInstance(instance);
        }
        public void AddDeactivatedInstance(object instance)
        {
            realCache.AddDeactivatedInstance(instance);
        }
        public void Clear()
        {
            realCache.Clear();
        }
        public bool IsActivated(object instance)
        {
            lock (realCache)
            {
                var fooClient = instance as FooClient;
                if (fooClient != null) return fooClient.Active;
                return realCache.IsActivated(instance);
            }
        }
        public bool IsDeactivated(object instance)
        {
            lock (realCache)
            {
                var fooClient = instance as FooClient;
                if (fooClient != null) return !fooClient.Active;
                return realCache.IsDeactivated(instance);
            }
        }
        public Ninject.INinjectSettings Settings
        {
            get
            {
                return realCache.Settings;
            }
            set
            {
                realCache.Settings = value;
            }
        }
        public void Prune()
        {
            realCache.Prune();
        }
    }
    
    // Wire it up:
    kernel.Components.RemoveAll<IActivationCache>();
    kernel.Components.Add<IActivationCache, PooledFooClientActivationCache>();
    
  3. 特别针对ServiceStack.Redis:使用PooledRedisClientManager.DisposablePooledClient<RedisClient>包装器,所以我们总是得到一个新的对象实例。然后让客户端对象变成暂态的,因为包装器会处理它。这种方法不解决更广泛的概念池对象与Ninject,只修复它为ServiceStack.Redis。

    var clientManager = new PooledRedisClientManager();
    kernel.Bind<PooledRedisClientManager.DisposablePooledClient<RedisClient>>()
        .ToMethod(ctx => clientManager.GetDisposableClient<RedisClient>())
        .InRequestScope();
    kernel.Bind<IRedisClient>()
        .ToMethod(ctx => ctx.Kernel.Get<PooledRedisClientManager.DisposablePooledClient<RedisClient>>().Client)
        .InTransientScope();
    

其中一种方法比另一种更合适吗?

如何将Ninject配置为始终停用池引用

我没有使用Redis到目前为止,所以我不能告诉你如何正确地做到这一点。但是我可以给你一些一般性的输入:

dispose并不是ActivationPipeline所做的唯一事情。(例如,它也做属性/方法注入和执行激活/停用动作。)使用自定义激活缓存返回false,即使它之前已经被激活,将导致这些其他动作再次执行(例如导致属性注入再次完成)