由於是基於.net-core平台,所以,我們最好是基於IDistributedCache接口來實現。ASP.NET-CORE下的官方redis客戶端實現是基於StackExchange的。但是官方提供的IDistributeCache接口中的方法只是增刪改查,我們可以繼續拓展,增加訂閱/發布,消息隊列,當然這些方法必須是基於底層的StackExchange相對應的方法來做的。
如果我們要實現自己的Redis客戶端,同時不使用底層的StackExchange驅動,可以派生一個繼承自IDistributedCache的接口,定義自己需要的方法,例如:
public interface IServiceStackRedisCache : IDistributedCache { void Delete<T>(T item); // 刪除 void DeleteAll<T>(T item); T Get<T>(string id); IQueryable<T> GetAll<T>(); IQueryable<T> GetAll<T>(string hash, string value); IQueryable<T> GetAll<T>(string hash, string value, Expression<Func<T, bool>> filter); long PublishMessage(string channel, object item); void Set<T>(T item); void Set<T>(T item, List<string> hash, List<string> value, string keyName); void Set<T>(T item, string hash, string value, string keyName); void SetAll<T>(List<T> listItems); void SetAll<T>(List<T> list, List<string> hash, List<string> value, string keyName); void SetAll<T>(List<T> list, string hash, string value, string keyName); }
接口有了,接下來就是繼承自接口的類,我們定義一個類來實現接口里的方法,例如:
using ServiceStack.Redis; namespace Microsoft.Extensions.Caching.Redis { public class ServiceStackRedisCache : IServiceStackRedisCache { private readonly IRedisClientsManager _redisManager; private readonly ServiceStackRedisCacheOptions _options; public ServiceStackRedisCache(IOptions<ServiceStackRedisCacheOptions> optionsAccessor) { if (optionsAccessor == null) { throw new ArgumentNullException(nameof(optionsAccessor)); } _options = optionsAccessor.Value; var host = $"{_options.Password}@{_options.Host}:{_options.Port}"; RedisConfig.VerifyMasterConnections = false; _redisManager = new RedisManagerPool(host); } #region Base public byte[] Get(string key) { if (key == null) { throw new ArgumentNullException(nameof(key)); } using (var client = _redisManager.GetClient() as IRedisNativeClient) { if (client.Exists(key) == 1) { return client.Get(key); } } return null; } public async Task<byte[]> GetAsync(string key) { return Get(key); } public void Set(string key, byte[] value, DistributedCacheEntryOptions options) { if (key == null) { throw new ArgumentNullException(nameof(key)); } if (value == null) { throw new ArgumentNullException(nameof(value)); } if (options == null) { throw new ArgumentNullException(nameof(options)); } using (var client = _redisManager.GetClient() as IRedisNativeClient) { var expireInSeconds = GetExpireInSeconds(options); if (expireInSeconds > 0) { client.SetEx(key, expireInSeconds, value); client.SetEx(GetExpirationKey(key), expireInSeconds, Encoding.UTF8.GetBytes(expireInSeconds.ToString())); } else { client.Set(key, value); } } } public async Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options) { Set(key, value, options); } public void Refresh(string key) { if (key == null) { throw new ArgumentNullException(nameof(key)); } using (var client = _redisManager.GetClient() as IRedisNativeClient) { if (client.Exists(key) == 1) { var value = client.Get(key); if (value != null) { var expirationValue = client.Get(GetExpirationKey(key)); if (expirationValue != null) { client.Expire(key, int.Parse(Encoding.UTF8.GetString(expirationValue))); } } } } } public async Task RefreshAsync(string key) { if (key == null) { throw new ArgumentNullException(nameof(key)); } Refresh(key); } public void Remove(string key) { if (key == null) { throw new ArgumentNullException(nameof(key)); } using (var client = _redisManager.GetClient() as IRedisNativeClient) { client.Del(key); } } public async Task RemoveAsync(string key) { Remove(key); } private int GetExpireInSeconds(DistributedCacheEntryOptions options) { if (options.SlidingExpiration.HasValue) { return (int)options.SlidingExpiration.Value.TotalSeconds; } else if (options.AbsoluteExpiration.HasValue) { return (int)options.AbsoluteExpirationRelativeToNow.Value.TotalSeconds; } else { return 0; } } private string GetExpirationKey(string key) { return key + $"-{nameof(DistributedCacheEntryOptions)}"; } #endregion #region data public T Get<T>(string id) { using (var redisClient = _redisManager.GetClient()) { var redis = redisClient.As<T>(); return redis.GetById(id.ToLower()); } } public IQueryable<T> GetAll<T>() { using (var redisClient = _redisManager.GetClient()) { var redis = redisClient.As<T>(); return redis.GetAll().AsQueryable(); } } public IQueryable<T> GetAll<T>(string hash, string value, Expression<Func<T, bool>> filter) { var filtered = _redisManager.GetClient().GetAllEntriesFromHash(hash).Where(c => c.Value.Equals(value, StringComparison.CurrentCultureIgnoreCase)); var ids = filtered.Select(c => c.Key); var ret = _redisManager.GetClient().As<T>().GetByIds(ids).AsQueryable() .Where(filter); return ret; } public IQueryable<T> GetAll<T>(string hash, string value) { var filtered = _redisManager.GetClient().GetAllEntriesFromHash(hash).Where(c => c.Value.Equals(value, StringComparison.CurrentCultureIgnoreCase)); var ids = filtered.Select(c => c.Key); var ret = _redisManager.GetClient().As<T>().GetByIds(ids).AsQueryable(); return ret; } public void Set<T>(T item) { using (var redisClient = _redisManager.GetClient()) { var redis = redisClient.As<T>(); redis.Store(item); } } public void Set<T>(T item, string hash, string value, string keyName) { Type t = item.GetType(); PropertyInfo prop = t.GetProperty(keyName); _redisManager.GetClient().SetEntryInHash(hash, prop.GetValue(item).ToString(), value.ToLower()); _redisManager.GetClient().As<T>().Store(item); } public void Set<T>(T item, List<string> hash, List<string> value, string keyName) { Type t = item.GetType(); PropertyInfo prop = t.GetProperty(keyName); for (int i = 0; i < hash.Count; i++) { _redisManager.GetClient().SetEntryInHash(hash[i], prop.GetValue(item).ToString(), value[i].ToLower()); } _redisManager.GetClient().As<T>().Store(item); } public void SetAll<T>(List<T> listItems) { using (var redisClient = _redisManager.GetClient()) { var redis = redisClient.As<T>(); redis.StoreAll(listItems); } } public void SetAll<T>(List<T> list, string hash, string value, string keyName) { foreach (var item in list) { Type t = item.GetType(); PropertyInfo prop = t.GetProperty(keyName); _redisManager.GetClient().SetEntryInHash(hash, prop.GetValue(item).ToString(), value.ToLower()); _redisManager.GetClient().As<T>().StoreAll(list); } } public void SetAll<T>(List<T> list, List<string> hash, List<string> value, string keyName) { foreach (var item in list) { Type t = item.GetType(); PropertyInfo prop = t.GetProperty(keyName); for (int i = 0; i < hash.Count; i++) { _redisManager.GetClient().SetEntryInHash(hash[i], prop.GetValue(item).ToString(), value[i].ToLower()); } _redisManager.GetClient().As<T>().StoreAll(list); } } public void Delete<T>(T item) { using (var redisClient = _redisManager.GetClient()) { var redis = redisClient.As<T>(); redis.Delete(item); } } public void DeleteAll<T>(T item) { using (var redisClient = _redisManager.GetClient()) { var redis = redisClient.As<T>(); redis.DeleteAll(); } } public long PublishMessage(string channel, object item) { var ret = _redisManager.GetClient().PublishMessage(channel, JsonConvert.SerializeObject(item)); return ret; } #endregion } }
在這里我們使用ServiceStack來作為底層redis驅動。在構造函數中根據配置連接redis服務器。
aps.net-core給我們提供了強大的配置功能,使用強類型的Options,一般,我們實現一個繼承自IOptions<TOptions>的類。定義一些字段用來表示主機,端口等常規redis配置選項。由於IOptions接口定義了一個Value屬性,我們可以通過這個屬性來獲取配置類的實例。
然后我們在redis客戶端類中(也就是上面的ServiceStackRedisCache類),使用構造函數注入。這樣就能獲取到redis的配置了。
然后我們在控制器的構造函數中注入redis客戶端類實例:
private readonly IServiceStackRedisCache _cache; public ValuesController(IServiceStackRedisCache cache) { _cache = cache; }
如此,我們才能使用redis客戶端去操作redis服務器。
最后就是最重要的部分了。ASP.NET-CORE框架隨處可見的都是依賴注入。上面所有的程序,都是一個接口對應着一個類。所謂的依賴注入,其實就是繼承自接口的類的實例化過程,但是這個過程是解耦的!DI的作用主要就是用來解耦實例化的過程。
ASP.NET-CORE框架依賴注入部分是在ConfigureService中使用的。
從上面的過程中,我們看到有兩個構造函數的注入過程,因此,我們需要實現兩個DI,分別是配置類的DI和redis客戶端類的DI。
services.Configure(setupAction);
services.Add(ServiceDescriptor.Singleton<IServiceStackRedisCache, ServiceStackRedisCache>());
整個步驟就是:
1.定義接口,用於繼承IDistributedCache的接口。該接口主要封裝了基本的redis操作。
2.實現接口,實現redis的各個邏輯。
3.基於IOptions<TOptions>接口實現redis的常規配置。
4.在控制器的構造函數中注入。
5.依賴注入以上接口的實例。
完整demo下載:鏈接:https://pan.baidu.com/s/17w0c0y9_VF3TzvgilgazjQ 密碼:4u5e
