diff --git a/.gitignore b/.gitignore
index e3325d77..f5c884f7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,7 @@ csx
; Visual Studio user state
*.user
*.suo
+.vs
; VC++/C# debug info files
*.idb
diff --git a/Microsoft.CacheProviders.sln b/Microsoft.CacheProviders.sln
index ea3994b8..f21c9879 100644
--- a/Microsoft.CacheProviders.sln
+++ b/Microsoft.CacheProviders.sln
@@ -25,6 +25,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RedisOutputCacheProvider.Un
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RedisOutputCacheProvider.FunctionalTests", "test\OutputCacheProviderFunctionalTests\RedisOutputCacheProvider.FunctionalTests.csproj", "{0F2B2EE8-83D9-4442-B71A-73BC2033D323}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ObjectCache", "ObjectCache", "{264192AF-0B17-4E07-B093-DC7B3E504628}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RedisObjectCache", "src\RedisObjectCache\RedisObjectCache.csproj", "{63C645E9-906A-4463-9BA7-8CB52206480C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RedisObjectCache.FunctionalTests", "test\RedisObjectCache.FunctionalTests\RedisObjectCache.FunctionalTests.csproj", "{5C77A3EA-BD24-4093-A763-9358E837173C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RedisObjectCache.UnitTests", "test\RedisObjectCache.UnitTests\RedisObjectCache.UnitTests.csproj", "{C4CF15B8-F965-4192-9008-7244CFAA9A03}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -55,6 +63,18 @@ Global
{0F2B2EE8-83D9-4442-B71A-73BC2033D323}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0F2B2EE8-83D9-4442-B71A-73BC2033D323}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0F2B2EE8-83D9-4442-B71A-73BC2033D323}.Release|Any CPU.Build.0 = Release|Any CPU
+ {63C645E9-906A-4463-9BA7-8CB52206480C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {63C645E9-906A-4463-9BA7-8CB52206480C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {63C645E9-906A-4463-9BA7-8CB52206480C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {63C645E9-906A-4463-9BA7-8CB52206480C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5C77A3EA-BD24-4093-A763-9358E837173C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5C77A3EA-BD24-4093-A763-9358E837173C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5C77A3EA-BD24-4093-A763-9358E837173C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5C77A3EA-BD24-4093-A763-9358E837173C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C4CF15B8-F965-4192-9008-7244CFAA9A03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C4CF15B8-F965-4192-9008-7244CFAA9A03}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C4CF15B8-F965-4192-9008-7244CFAA9A03}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C4CF15B8-F965-4192-9008-7244CFAA9A03}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -66,5 +86,8 @@ Global
{A829BDCA-3B7D-4C22-AF85-C82012A8FE2F} = {A5E4A982-2782-43BF-871C-596106C96B14}
{9BC29B91-CCB5-499A-B68B-5AAC0492B7CB} = {A5E4A982-2782-43BF-871C-596106C96B14}
{0F2B2EE8-83D9-4442-B71A-73BC2033D323} = {A5E4A982-2782-43BF-871C-596106C96B14}
+ {63C645E9-906A-4463-9BA7-8CB52206480C} = {264192AF-0B17-4E07-B093-DC7B3E504628}
+ {5C77A3EA-BD24-4093-A763-9358E837173C} = {264192AF-0B17-4E07-B093-DC7B3E504628}
+ {C4CF15B8-F965-4192-9008-7244CFAA9A03} = {264192AF-0B17-4E07-B093-DC7B3E504628}
EndGlobalSection
EndGlobal
diff --git a/src/OutputCacheProvider/RedisOutputCacheConnectionWrapper.cs b/src/OutputCacheProvider/RedisOutputCacheConnectionWrapper.cs
index 74053a20..1e008c3b 100644
--- a/src/OutputCacheProvider/RedisOutputCacheConnectionWrapper.cs
+++ b/src/OutputCacheProvider/RedisOutputCacheConnectionWrapper.cs
@@ -11,7 +11,7 @@ internal class RedisOutputCacheConnectionWrapper : IOutputCacheConnection
{
internal static RedisSharedConnection sharedConnection;
static object lockForSharedConnection = new object();
- internal static RedisUtility redisUtility;
+ internal static RedisUtility redisUtility;
internal IRedisClientConnection redisConnection;
ProviderConfiguration configuration;
diff --git a/src/RedisObjectCache/IObjectCacheConnection.cs b/src/RedisObjectCache/IObjectCacheConnection.cs
new file mode 100644
index 00000000..cf1fc783
--- /dev/null
+++ b/src/RedisObjectCache/IObjectCacheConnection.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Globalization;
+
+namespace Microsoft.Web.Redis
+{
+ internal interface IObjectCacheConnection
+ {
+ void ResetExpiry(string key, DateTime utcExpiry, string regionName = null);
+ bool Exists(string key, string regionName = null);
+ void Set(string key, object entry, DateTime utcExpiry, string regionName = null);
+ object AddOrGetExisting(string key, object entry, DateTime utcExpiry, string regionName = null);
+ object Get(string key, string regionName = null);
+ object Remove(string key, string regionName = null);
+ }
+}
\ No newline at end of file
diff --git a/src/RedisObjectCache/Properties/AssemblyInfo.cs b/src/RedisObjectCache/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..b969d600
--- /dev/null
+++ b/src/RedisObjectCache/Properties/AssemblyInfo.cs
@@ -0,0 +1,56 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+//
+
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("RedisObjectCacheProvider")]
+[assembly: AssemblyCopyright("Copyright © 2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+[assembly: AssemblyMetadata("Serviceable", "True")]
+
+#if !CODESIGNING
+[assembly: InternalsVisibleTo("Microsoft.Web.RedisObjectCache.Unit.Tests")]
+[assembly: InternalsVisibleTo("Microsoft.Web.RedisObjectCache.Functional.Tests")]
+[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
+#endif
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+#if !NOCOMMONASSEMBLYVERSION
+[assembly: AssemblyVersion("1.1.0.0")]
+[assembly: AssemblyFileVersion("1.1.0.0")]
+#endif
+[assembly: AssemblyTitle("Cache Providers")]
+
+namespace System.Reflection
+{
+ ///
+ /// Provided as a down-level stub for the 4.5 AssemblyMetaDataAttribute class.
+ /// All released assemblies should define [AssemblyMetadata("Serviceable", "True")].
+ ///
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)]
+ internal sealed class AssemblyMetadataAttribute : Attribute
+ {
+ public AssemblyMetadataAttribute(string key, string value)
+ {
+ Key = key;
+ Value = value;
+ }
+
+ public string Key { get; set; }
+ public string Value { get; set; }
+ }
+}
diff --git a/src/RedisObjectCache/RedisObjectCache.cs b/src/RedisObjectCache/RedisObjectCache.cs
new file mode 100644
index 00000000..f99ea55f
--- /dev/null
+++ b/src/RedisObjectCache/RedisObjectCache.cs
@@ -0,0 +1,253 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Configuration;
+using System.Globalization;
+using System.Runtime.Caching;
+
+namespace Microsoft.Web.Redis
+{
+ public class RedisObjectCache : ObjectCache
+ {
+ // static holder for instance, need to use lambda to construct since constructor private
+ private static readonly Lazy Instance = new Lazy(() => new RedisObjectCache());
+
+ private static readonly TimeSpan OneYear = TimeSpan.FromDays(365);
+
+ internal IObjectCacheConnection cache;
+ private readonly ProviderConfiguration configuration;
+
+ private RedisObjectCache()
+ : this("Default")
+ {
+ }
+
+ public RedisObjectCache(string name)
+ : this (name, GetConfig(name))
+ {
+ }
+
+ public RedisObjectCache(string name, string connectionString)
+ : this (name, new NameValueCollection { { "connectionString", connectionString } })
+ {
+ }
+
+ internal RedisObjectCache(string name, NameValueCollection config)
+ {
+ if (string.IsNullOrEmpty(name))
+ throw new ArgumentNullException(nameof(name));
+ if (config == null)
+ throw new ArgumentNullException(nameof(config));
+
+ Name = name;
+ configuration = ProviderConfiguration.ProviderConfigurationForObjectCache(config, name);
+ cache = new RedisObjectCacheConnectionWrapper(configuration, name);
+ }
+
+ private static NameValueCollection GetConfig(string name)
+ {
+ if (string.IsNullOrEmpty(name))
+ throw new ArgumentNullException(nameof(name));
+
+ ProviderSettings providerSettings = RedisObjectCacheConfiguration.Instance.Caches[name];
+
+ if (providerSettings == null)
+ throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, RedisProviderResource.NoConfigForCache, name), nameof(name));
+
+ NameValueCollection pars = providerSettings.Parameters;
+ NameValueCollection config = new NameValueCollection(pars.Count, StringComparer.Ordinal);
+ foreach (string key in pars)
+ config[key] = pars[key];
+ return config;
+ }
+
+ public static RedisObjectCache Default => Instance.Value;
+
+ public override DefaultCacheCapabilities DefaultCacheCapabilities { get; } = DefaultCacheCapabilities.AbsoluteExpirations
+ | DefaultCacheCapabilities.SlidingExpirations
+ | DefaultCacheCapabilities.OutOfProcessProvider
+ | DefaultCacheCapabilities.CacheRegions;
+
+ public override string Name { get; }
+
+ public override object this[string key]
+ {
+ get { return Get(key); }
+ set { Set(key, value, InfiniteAbsoluteExpiration); }
+ }
+
+ public override bool Contains(string key, string regionName = null)
+ {
+ return cache.Exists(key, regionName);
+ }
+
+ public override object AddOrGetExisting(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null)
+ {
+ CacheItemPolicy policy = new CacheItemPolicy { AbsoluteExpiration = absoluteExpiration };
+ return AddOrGetExisting(key, value, policy, regionName);
+ }
+
+ public override CacheItem AddOrGetExisting(CacheItem value, CacheItemPolicy policy)
+ {
+ return new CacheItem(value.Key, AddOrGetExisting(value.Key, value.Value, policy), value.RegionName);
+ }
+
+ public override object AddOrGetExisting(string key, object value, CacheItemPolicy policy, string regionName = null)
+ {
+ ValidatePolicy(policy);
+ try
+ {
+ DateTime utcExpiry;
+ if (policy.SlidingExpiration != NoSlidingExpiration)
+ {
+ utcExpiry = DateTime.UtcNow + policy.SlidingExpiration;
+ value = new SlidingExpiryCacheItem(value, policy.SlidingExpiration);
+ }
+ else
+ utcExpiry = policy.AbsoluteExpiration.UtcDateTime;
+
+ object oldValue = cache.AddOrGetExisting(key, value, utcExpiry, regionName);
+ oldValue = HandleSlidingExpiry(key, oldValue, regionName);
+ return oldValue;
+ }
+ catch (Exception e)
+ {
+ LogUtility.LogError("Error in AddOrGetExisting: " + e.Message);
+ if (configuration.ThrowOnError)
+ throw;
+ }
+ return null;
+ }
+
+ public override CacheItem GetCacheItem(string key, string regionName = null)
+ {
+ return new CacheItem(key, Get(key, regionName), regionName);
+ }
+
+ public override object Get(string key, string regionName = null)
+ {
+ try
+ {
+ object value = cache.Get(key, regionName);
+ value = HandleSlidingExpiry(key, value, regionName);
+ return value;
+ }
+ catch (Exception e)
+ {
+ LogUtility.LogError("Error in Get: " + e.Message);
+ if (configuration.ThrowOnError)
+ throw;
+ }
+ return null;
+ }
+
+ public override void Set(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null)
+ {
+ CacheItemPolicy policy = new CacheItemPolicy { AbsoluteExpiration = absoluteExpiration };
+ Set(key, value, policy, regionName);
+ }
+ public override void Set(CacheItem item, CacheItemPolicy policy)
+ {
+ Set(item.Key, item.Value, policy, item.RegionName);
+ }
+
+ public override void Set(string key, object value, CacheItemPolicy policy, string regionName = null)
+ {
+ ValidatePolicy(policy);
+ try
+ {
+ DateTime utcExpiry;
+ if (policy.SlidingExpiration != NoSlidingExpiration)
+ {
+ utcExpiry = DateTime.UtcNow + policy.SlidingExpiration;
+ value = new SlidingExpiryCacheItem(value, policy.SlidingExpiration);
+ }
+ else
+ utcExpiry = policy.AbsoluteExpiration.UtcDateTime;
+
+ cache.Set(key, value, utcExpiry, regionName);
+ }
+ catch (Exception e)
+ {
+ LogUtility.LogError("Error in Set: " + e.Message);
+ if (configuration.ThrowOnError)
+ throw;
+ }
+ }
+
+ public override object Remove(string key, string regionName = null)
+ {
+ try
+ {
+ return cache.Remove(key, regionName);
+ }
+ catch (Exception e)
+ {
+ LogUtility.LogError("Error in Remove: " + e.Message);
+ if (configuration.ThrowOnError)
+ throw;
+ }
+ return null;
+ }
+
+ private object HandleSlidingExpiry(string key, object value, string regionName)
+ {
+ SlidingExpiryCacheItem item = value as SlidingExpiryCacheItem;
+
+ if (item == null)
+ return value;
+
+ cache.ResetExpiry(key, DateTime.UtcNow + item.SlidingExpiration, regionName);
+ return item.Value;
+ }
+
+ private void ValidatePolicy(CacheItemPolicy policy)
+ {
+ if (policy.RemovedCallback != null)
+ throw new NotSupportedException(RedisProviderResource.RemovedCallbackNotSupported);
+
+ if (policy.UpdateCallback != null)
+ throw new NotSupportedException(RedisProviderResource.UpdateCallbackNotSupported);
+
+ if (policy.ChangeMonitors.Count != 0)
+ throw new NotSupportedException(RedisProviderResource.ChangeMonitorsNotSupported);
+
+ if (policy.Priority == CacheItemPriority.NotRemovable)
+ throw new NotSupportedException(RedisProviderResource.NotRemovableNotSupported);
+
+ // copied from MemoryCache.ValidatPolicy()
+ if (policy.AbsoluteExpiration != InfiniteAbsoluteExpiration && policy.SlidingExpiration != NoSlidingExpiration)
+ throw new ArgumentException(RedisProviderResource.InvalidExpirationPolicy , nameof(policy));
+
+ if (policy.SlidingExpiration < NoSlidingExpiration || OneYear < policy.SlidingExpiration)
+ throw new ArgumentOutOfRangeException(nameof(policy));
+
+ if (policy.Priority != CacheItemPriority.Default && policy.Priority != CacheItemPriority.NotRemovable)
+ throw new ArgumentOutOfRangeException(nameof(policy));
+ }
+
+ #region Not Implemented
+
+ public override IDictionary GetValues(IEnumerable keys, string regionName = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override long GetCount(string regionName = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ protected override IEnumerator> GetEnumerator()
+ {
+ throw new NotImplementedException();
+ }
+
+ public override CacheEntryChangeMonitor CreateCacheEntryChangeMonitor(IEnumerable keys, string regionName = null)
+ {
+ throw new NotSupportedException(RedisProviderResource.ChangeMonitorsNotSupported);
+ }
+
+ #endregion
+ }
+}
diff --git a/src/RedisObjectCache/RedisObjectCache.csproj b/src/RedisObjectCache/RedisObjectCache.csproj
new file mode 100644
index 00000000..75765c6c
--- /dev/null
+++ b/src/RedisObjectCache/RedisObjectCache.csproj
@@ -0,0 +1,154 @@
+
+
+
+
+
+ Debug
+ AnyCPU
+ {63C645E9-906A-4463-9BA7-8CB52206480C}
+ Library
+ Properties
+ Microsoft.Web.Redis
+ Microsoft.Caching.RedisObjectCache
+ v4.0
+ 512
+
+ true
+ true
+ dummy.snk
+
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll
+ True
+
+
+ ..\..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll
+ True
+
+
+ ..\..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll
+ True
+
+
+ ..\..\packages\StackExchange.Redis.StrongName.1.2.1\lib\net40\StackExchange.Redis.StrongName.dll
+ True
+
+
+
+
+ ..\..\packages\Microsoft.Bcl.1.1.10\lib\net40\System.IO.dll
+ True
+
+
+
+ ..\..\packages\Microsoft.Bcl.1.1.10\lib\net40\System.Runtime.dll
+ True
+
+
+
+
+ ..\..\packages\Microsoft.Bcl.1.1.10\lib\net40\System.Threading.Tasks.dll
+ True
+
+
+
+
+
+
+ BinarySerializer.cs
+
+
+ ChangeTrackingSessionStateItemCollection.cs
+
+
+ IRedisClientConnection.cs
+
+
+ ISerializer.cs
+
+
+ LogUtility.cs
+
+
+ ProviderConfiguration.cs
+
+
+ RedisNull.cs
+
+
+ RedisSharedConnection.cs
+
+
+ RedisUtility.cs
+
+
+ StackExchangeClientConnection.cs
+
+
+ ValueWrapper.cs
+
+
+
+
+
+
+
+ RedisProviderResource.resx
+ True
+ True
+
+
+
+
+
+
+
+
+
+ ResXFileCodeGenerator
+ RedisProviderResource.Designer.cs
+
+
+
+
+ $(OutputPath)$(TargetFrameworkVersion)\
+
+
+ $(OutputPath)$(TargetFrameworkVersion)\
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
+
+
\ No newline at end of file
diff --git a/src/RedisObjectCache/RedisObjectCacheConfiguration.cs b/src/RedisObjectCache/RedisObjectCacheConfiguration.cs
new file mode 100644
index 00000000..f0b2c9b5
--- /dev/null
+++ b/src/RedisObjectCache/RedisObjectCacheConfiguration.cs
@@ -0,0 +1,59 @@
+using System.Configuration;
+using System.Linq;
+using System.Xml;
+
+namespace Microsoft.Web.Redis
+{
+ // Use the following web.config file.
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ public sealed class RedisObjectCacheConfiguration : ConfigurationSection
+ {
+ // Properties
+ public static RedisObjectCacheConfiguration Instance
+ {
+ get { return (RedisObjectCacheConfiguration)ConfigurationManager.GetSection("redisObjectCache"); }
+ }
+
+ protected override void DeserializeSection(XmlReader reader)
+ {
+ // ProviderSettingsCollection and ProviderSettings needs to have a type-attribute on the add-element
+ // for the cache configuration type is not needed. To prevent the user from having to add an empty
+ // type-attribute, this piece of code just adds an empty attribute.
+ reader.Read();
+ string xml = reader.ReadOuterXml();
+ XmlDocument document = new XmlDocument();
+ document.LoadXml(xml);
+
+ foreach (XmlElement childNode in document.DocumentElement.SelectNodes("//add[not(@type)]").OfType())
+ childNode.SetAttribute("type", string.Empty);
+
+ using (XmlReader innerReader = new XmlNodeReader(document))
+ base.DeserializeSection(innerReader);
+ }
+
+ [ConfigurationProperty("caches", IsRequired = true)]
+ public ProviderSettingsCollection Caches
+ {
+ get { return (ProviderSettingsCollection)this["caches"]; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/RedisObjectCache/RedisObjectCacheConnectionWrapper.cs b/src/RedisObjectCache/RedisObjectCacheConnectionWrapper.cs
new file mode 100644
index 00000000..f9066015
--- /dev/null
+++ b/src/RedisObjectCache/RedisObjectCacheConnectionWrapper.cs
@@ -0,0 +1,103 @@
+using System;
+using System.Collections.Generic;
+
+namespace Microsoft.Web.Redis
+{
+ internal class RedisObjectCacheConnectionWrapper : IObjectCacheConnection
+ {
+ private static Dictionary sharedConnections = new Dictionary();
+ private static object lockForSharedConnection = new object();
+ internal static RedisUtility redisUtility;
+
+ private IRedisClientConnection redisConnection;
+ private ProviderConfiguration configuration;
+
+ public RedisObjectCacheConnectionWrapper(ProviderConfiguration configuration, string name)
+ {
+ this.configuration = configuration;
+
+ // Shared connection is created by server when it starts. don't want to lock everytime when check == null.
+ // so that is why pool == null exists twice.
+ if (!sharedConnections.ContainsKey(name))
+ {
+ lock (lockForSharedConnection)
+ {
+ if (!sharedConnections.ContainsKey(name))
+ {
+ sharedConnections[name] = new RedisSharedConnection(configuration, () => new StackExchangeClientConnection(configuration));
+ redisUtility = new RedisUtility(configuration);
+ }
+ }
+ }
+ redisConnection = sharedConnections[name].TryGetConnection();
+ }
+
+ /*-------Start of Add operation-----------------------------------------------------------------------------------------------------------------------------------------------*/
+ // KEYS = { key }
+ // ARGV = { page data, expiry time in miliseconds }
+ // retArray = { page data from cache or new }
+ static readonly string addScript = (@"
+ local retVal = redis.call('GET',KEYS[1])
+ if retVal == false then
+ redis.call('PSETEX',KEYS[1],ARGV[2],ARGV[1])
+ retVal = nil
+ end
+ return retVal
+ ");
+
+ public object AddOrGetExisting(string key, object entry, DateTime utcExpiry, string regionName = null)
+ {
+ key = GetKeyForRedis(key, regionName);
+ TimeSpan expiryTime = utcExpiry - DateTime.UtcNow;
+ string[] keyArgs = new string[] { key };
+ object[] valueArgs = new object[] { redisUtility.GetBytesFromObject(entry), (long)expiryTime.TotalMilliseconds };
+
+ object rowDataFromRedis = redisConnection.Eval(addScript, keyArgs, valueArgs);
+ return redisUtility.GetObjectFromBytes(redisConnection.GetOutputCacheDataFromResult(rowDataFromRedis));
+ }
+
+ /*-------End of Add operation-----------------------------------------------------------------------------------------------------------------------------------------------*/
+
+ public void ResetExpiry(string key, DateTime utcExpiry, string regionName = null)
+ {
+ key = GetKeyForRedis(key, regionName);
+ redisConnection.Expiry(key, (int)(utcExpiry - DateTime.UtcNow).TotalSeconds);
+ }
+
+ public bool Exists(string key, string regionName = null)
+ {
+ key = GetKeyForRedis(key, regionName);
+ return redisConnection.Exists(key);
+ }
+
+ public void Set(string key, object entry, DateTime utcExpiry, string regionName = null)
+ {
+ key = GetKeyForRedis(key, regionName);
+ byte[] data = redisUtility.GetBytesFromObject(entry);
+ redisConnection.Set(key, data, utcExpiry);
+ }
+
+ public object Get(string key, string regionName = null)
+ {
+ key = GetKeyForRedis(key, regionName);
+ byte[] data = redisConnection.Get(key);
+ return redisUtility.GetObjectFromBytes(data);
+ }
+
+ public object Remove(string key, string regionName = null)
+ {
+ key = GetKeyForRedis(key, regionName);
+ object value = Get(key, regionName);
+ redisConnection.Remove(key);
+ return value;
+ }
+
+ private string GetKeyForRedis(string key, string regionName = null)
+ {
+ if (regionName != null)
+ regionName += "_";
+
+ return configuration.ApplicationName + "_" + regionName + key;
+ }
+ }
+}
diff --git a/src/RedisObjectCache/RedisProviderResource.Designer.cs b/src/RedisObjectCache/RedisProviderResource.Designer.cs
new file mode 100644
index 00000000..5a178b03
--- /dev/null
+++ b/src/RedisObjectCache/RedisProviderResource.Designer.cs
@@ -0,0 +1,162 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Microsoft.Web.Redis {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class RedisProviderResource {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal RedisProviderResource() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Web.Redis.RedisProviderResource", typeof(RedisProviderResource).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Change Monitors are not supported.
+ ///
+ internal static string ChangeMonitorsNotSupported {
+ get {
+ return ResourceManager.GetString("ChangeMonitorsNotSupported", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The specified class '{0}' could not be loaded. Please make sure that the value specified is an assembly qualified class name..
+ ///
+ internal static string ClassNotFound {
+ get {
+ return ResourceManager.GetString("ClassNotFound", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Either use the combination of parameters "settingsClassName" and "settingsMethodName" to provide the value of connection string or use the parameter "connectionString" but not both..
+ ///
+ internal static string ConnectionStringException {
+ get {
+ return ResourceManager.GetString("ConnectionStringException", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Invalid expiration combination. Either set an absolute expiration or a sliding expiration..
+ ///
+ internal static string InvalidExpirationPolicy {
+ get {
+ return ResourceManager.GetString("InvalidExpirationPolicy", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The specified method '{0}' on the class '{1}' could not be found or does meet the required method signature. Please make sure that it exists, is public and doesn't take any parameters..
+ ///
+ internal static string MethodNotFound {
+ get {
+ return ResourceManager.GetString("MethodNotFound", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The specified method '{0}' on the class '{1}' does not match the required method signature. The method must be defined as \"static\"..
+ ///
+ internal static string MethodNotStatic {
+ get {
+ return ResourceManager.GetString("MethodNotStatic", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The specified method '{0}' on the class '{1}' does not match the required method signature. The method must have a return type of \"{2}\"..
+ ///
+ internal static string MethodWrongReturnType {
+ get {
+ return ResourceManager.GetString("MethodWrongReturnType", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to No configuration has been supplied for Redis Object Cache with name '{0}'.
+ ///
+ internal static string NoConfigForCache {
+ get {
+ return ResourceManager.GetString("NoConfigForCache", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to NotRemovable is not supported as Cache item priority.
+ ///
+ internal static string NotRemovableNotSupported {
+ get {
+ return ResourceManager.GetString("NotRemovableNotSupported", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Removed Callback is not supported.
+ ///
+ internal static string RemovedCallbackNotSupported {
+ get {
+ return ResourceManager.GetString("RemovedCallbackNotSupported", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Update Callback is not supported.
+ ///
+ internal static string UpdateCallbackNotSupported {
+ get {
+ return ResourceManager.GetString("UpdateCallbackNotSupported", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/RedisObjectCache/RedisProviderResource.resx b/src/RedisObjectCache/RedisProviderResource.resx
new file mode 100644
index 00000000..76da4adf
--- /dev/null
+++ b/src/RedisObjectCache/RedisProviderResource.resx
@@ -0,0 +1,153 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Change Monitors are not supported
+
+
+ The specified class '{0}' could not be loaded. Please make sure that the value specified is an assembly qualified class name.
+
+
+ Either use the combination of parameters "settingsClassName" and "settingsMethodName" to provide the value of connection string or use the parameter "connectionString" but not both.
+
+
+ Invalid expiration combination. Either set an absolute expiration or a sliding expiration.
+
+
+ The specified method '{0}' on the class '{1}' could not be found or does meet the required method signature. Please make sure that it exists, is public and doesn't take any parameters.
+
+
+ The specified method '{0}' on the class '{1}' does not match the required method signature. The method must be defined as \"static\".
+
+
+ The specified method '{0}' on the class '{1}' does not match the required method signature. The method must have a return type of \"{2}\".
+
+
+ No configuration has been supplied for Redis Object Cache with name '{0}'
+
+
+ NotRemovable is not supported as Cache item priority
+
+
+ Removed Callback is not supported
+
+
+ Update Callback is not supported
+
+
\ No newline at end of file
diff --git a/src/RedisObjectCache/SlidingExpiryCacheItem.cs b/src/RedisObjectCache/SlidingExpiryCacheItem.cs
new file mode 100644
index 00000000..f034c981
--- /dev/null
+++ b/src/RedisObjectCache/SlidingExpiryCacheItem.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace Microsoft.Web.Redis
+{
+ [Serializable]
+ internal class SlidingExpiryCacheItem
+ {
+ public SlidingExpiryCacheItem(object value, TimeSpan slidingExpiration)
+ {
+ Value = value;
+ SlidingExpiration = slidingExpiration;
+ }
+
+ public object Value { get; }
+
+ public TimeSpan SlidingExpiration { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/RedisObjectCache/app.config b/src/RedisObjectCache/app.config
new file mode 100644
index 00000000..e03ea0a5
--- /dev/null
+++ b/src/RedisObjectCache/app.config
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/RedisObjectCache/packages.config b/src/RedisObjectCache/packages.config
new file mode 100644
index 00000000..96294b2e
--- /dev/null
+++ b/src/RedisObjectCache/packages.config
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Shared/IRedisClientConnection.cs b/src/Shared/IRedisClientConnection.cs
index fe63cc86..60339378 100644
--- a/src/Shared/IRedisClientConnection.cs
+++ b/src/Shared/IRedisClientConnection.cs
@@ -18,6 +18,7 @@ internal interface IRedisClientConnection
int GetSessionTimeout(object rowDataFromRedis);
bool IsLocked(object rowDataFromRedis);
ISessionStateItemCollection GetSessionData(object rowDataFromRedis);
+ bool Exists(string key);
void Set(string key, byte[] data, DateTime utcExpiry);
byte[] Get(string key);
void Remove(string key);
diff --git a/src/Shared/ProviderConfiguration.cs b/src/Shared/ProviderConfiguration.cs
index c3423252..cdf9468c 100644
--- a/src/Shared/ProviderConfiguration.cs
+++ b/src/Shared/ProviderConfiguration.cs
@@ -72,6 +72,24 @@ internal static ProviderConfiguration ProviderConfigurationForOutputCache(NameVa
return configuration;
}
+ internal static ProviderConfiguration ProviderConfigurationForObjectCache(NameValueCollection config, string cacheName)
+ {
+ ProviderConfiguration configuration = new ProviderConfiguration(config);
+
+ configuration.RetryTimeout = TimeSpan.Zero;
+ configuration.ThrowOnError = GetBoolSettings(config, "throwOnError", false);
+
+ // Session state specific attribute which are not applicable to output cache
+ configuration.RequestTimeout = TimeSpan.Zero;
+ configuration.SessionTimeout = TimeSpan.Zero;
+
+ configuration.ApplicationName += "_ObjectCache_" + cacheName;
+
+ LogUtility.LogInfo("Host: {0}, Port: {1}, UseSsl: {2}, DatabaseId: {3}, ApplicationName: {4}, ThrowOnError: {5}",
+ configuration.Host, configuration.Port, configuration.UseSsl, configuration.DatabaseId, configuration.ApplicationName, configuration.ThrowOnError);
+ return configuration;
+ }
+
private ProviderConfiguration(NameValueCollection config)
{
EnableLoggingIfParametersAvailable(config);
diff --git a/src/Shared/RedisSharedConnection.cs b/src/Shared/RedisSharedConnection.cs
index 292e96b3..dddac274 100644
--- a/src/Shared/RedisSharedConnection.cs
+++ b/src/Shared/RedisSharedConnection.cs
@@ -33,7 +33,7 @@ public IRedisClientConnection TryGetConnection()
//case 2: we are allowed to create first connection
lock (lockObject)
{
- // make suer it is not created by other request in between
+ // make sure it is not created by other request in between
if (connection == null)
{
connection = factory.Invoke();
diff --git a/src/Shared/StackExchangeClientConnection.cs b/src/Shared/StackExchangeClientConnection.cs
index c7a58fb9..5f1a9dc8 100644
--- a/src/Shared/StackExchangeClientConnection.cs
+++ b/src/Shared/StackExchangeClientConnection.cs
@@ -243,6 +243,12 @@ public ISessionStateItemCollection GetSessionData(object rowDataFromRedis)
return sessionData;
}
+ public bool Exists(string key)
+ {
+ RedisKey redisKey = key;
+ return connection.KeyExists(redisKey);
+ }
+
public void Set(string key, byte[] data, DateTime utcExpiry)
{
RedisKey redisKey = key;
diff --git a/test/RedisObjectCache.FunctionalTests/Properties/AssemblyInfo.cs b/test/RedisObjectCache.FunctionalTests/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..766150f3
--- /dev/null
+++ b/test/RedisObjectCache.FunctionalTests/Properties/AssemblyInfo.cs
@@ -0,0 +1,38 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Xunit;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("RedisObjectCache.FunctionalTests")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("RedisObjectCache.FunctionalTests")]
+[assembly: AssemblyCopyright("Copyright © 2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+[assembly: CollectionBehavior(DisableTestParallelization = true)]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("5c77a3ea-bd24-4093-a763-9358e837173c")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/test/RedisObjectCache.FunctionalTests/RedisObjectCache.FunctionalTests.csproj b/test/RedisObjectCache.FunctionalTests/RedisObjectCache.FunctionalTests.csproj
new file mode 100644
index 00000000..d5abe14f
--- /dev/null
+++ b/test/RedisObjectCache.FunctionalTests/RedisObjectCache.FunctionalTests.csproj
@@ -0,0 +1,118 @@
+
+
+
+
+
+
+ Debug
+ AnyCPU
+ {5C77A3EA-BD24-4093-A763-9358E837173C}
+ Library
+ Properties
+ Microsoft.Web.Redis.FunctionalTests
+ Microsoft.Web.RedisObjectCache.Functional.Tests
+ v4.5
+ 512
+
+
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\..\packages\StackExchange.Redis.StrongName.1.2.1\lib\net45\StackExchange.Redis.StrongName.dll
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll
+ True
+
+
+ ..\..\packages\xunit.assert.2.1.0\lib\dotnet\xunit.assert.dll
+ True
+
+
+ ..\..\packages\xunit.extensibility.core.2.1.0\lib\dotnet\xunit.core.dll
+ True
+
+
+ ..\..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.dll
+ True
+
+
+
+
+ RedisServer.cs
+
+
+
+
+
+
+ {63c645e9-906a-4463-9ba7-8cb52206480c}
+ RedisObjectCache
+
+
+
+
+
+
+
+ Designer
+
+
+ Designer
+
+
+
+
+ $(OutputPath)$(TargetFrameworkVersion)\
+ false
+
+
+ $(OutputPath)$(TargetFrameworkVersion)\
+ false
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
+
+
\ No newline at end of file
diff --git a/test/RedisObjectCache.FunctionalTests/RedisObjectCacheFunctionalTests.cs b/test/RedisObjectCache.FunctionalTests/RedisObjectCacheFunctionalTests.cs
new file mode 100644
index 00000000..abdf5bb5
--- /dev/null
+++ b/test/RedisObjectCache.FunctionalTests/RedisObjectCacheFunctionalTests.cs
@@ -0,0 +1,277 @@
+using System;
+using System.Collections.Specialized;
+using System.Runtime.Caching;
+using Xunit;
+
+namespace Microsoft.Web.Redis.FunctionalTests
+{
+ public class RedisObjectCacheFunctionalTests
+ {
+ [Fact]
+ public void NameTest()
+ {
+ NameValueCollection config = new NameValueCollection();
+ config.Add("ssl", "false");
+ RedisObjectCache provider = new RedisObjectCache("test", config);
+
+ Assert.Equal("test", provider.Name);
+ }
+
+ [Fact]
+ public void GetWithoutSetTest()
+ {
+ using (new RedisServer())
+ {
+ NameValueCollection config = new NameValueCollection();
+ config.Add("ssl", "false");
+ RedisObjectCache provider = new RedisObjectCache("test", config);
+
+ Assert.Equal(null, provider.Get("key1"));
+ }
+ }
+
+ [Fact]
+ public void SetGetTest()
+ {
+ using (new RedisServer())
+ {
+ NameValueCollection config = new NameValueCollection();
+ config.Add("ssl", "false");
+ RedisObjectCache provider = new RedisObjectCache("test", config);
+
+ DateTime utxExpiry = DateTime.UtcNow.AddSeconds(3);
+ provider.Set("key2", "data2", utxExpiry, "testRegion");
+ object data = provider.Get("key2", "testRegion");
+ Assert.Equal("data2", data);
+ }
+ }
+
+ [Fact]
+ public void SetGetIndexerTest()
+ {
+ using (new RedisServer())
+ {
+ NameValueCollection config = new NameValueCollection();
+ config.Add("ssl", "false");
+ RedisObjectCache provider = new RedisObjectCache("test", config);
+
+ DateTime utxExpiry = DateTime.UtcNow.AddSeconds(3);
+ provider["key2"] = "data2";
+ object data = provider["key2"];
+ Assert.Equal("data2", data);
+ }
+ }
+
+ [Fact]
+ public void AddWithExistingSetTest()
+ {
+ using (new RedisServer())
+ {
+ NameValueCollection config = new NameValueCollection();
+ config.Add("ssl", "false");
+ RedisObjectCache provider = new RedisObjectCache("test", config);
+
+ DateTime utxExpiry = DateTime.UtcNow.AddSeconds(3);
+ provider.Set("key3", "data3", utxExpiry, "testRegion");
+ Assert.False(provider.Add("key3", "data3.1", utxExpiry, "testRegion"));
+ object data = provider.Get("key3", "testRegion");
+ Assert.Equal("data3", data);
+ }
+ }
+
+ [Fact]
+ public void AddWithoutSetTest()
+ {
+ using (new RedisServer())
+ {
+ NameValueCollection config = new NameValueCollection();
+ config.Add("ssl", "false");
+
+ RedisObjectCache provider = new RedisObjectCache("test", config);
+
+ DateTime utxExpiry = DateTime.UtcNow.AddSeconds(3);
+ Assert.True(provider.Add("key4", "data4", utxExpiry, "testRegion"));
+ object data = provider.Get("key4", "testRegion");
+ Assert.Equal("data4", data);
+ }
+ }
+
+ [Fact]
+ public void AddWhenSetExpiresTest()
+ {
+ using (new RedisServer())
+ {
+ NameValueCollection config = new NameValueCollection();
+ config.Add("ssl", "false");
+ RedisObjectCache provider = new RedisObjectCache("test", config);
+
+ DateTime utxExpiry = DateTime.UtcNow.AddSeconds(1);
+ provider.Set("key5", "data5", utxExpiry, "testRegion");
+ object data = provider.Get("key5", "testRegion");
+ Assert.Equal("data5", data);
+
+ // Wait for 1.1 seconds so that data will expire
+ System.Threading.Thread.Sleep(1100);
+ utxExpiry = DateTime.UtcNow.AddSeconds(3);
+ Assert.True(provider.Add("key5", "data5.1", utxExpiry, "testRegion"));
+ data = provider.Get("key5", "testRegion");
+ Assert.Equal("data5.1", data);
+ }
+ }
+
+ [Fact]
+ public void RemoveWithoutSetTest()
+ {
+ using (new RedisServer())
+ {
+ NameValueCollection config = new NameValueCollection();
+ config.Add("ssl", "false");
+ RedisObjectCache provider = new RedisObjectCache("test", config);
+
+ provider.Remove("key6", "testRegion");
+ object data = provider.Get("key6", "testRegion");
+ Assert.Equal(null, data);
+ }
+ }
+
+ [Fact]
+ public void RemoveTest()
+ {
+ using (new RedisServer())
+ {
+ NameValueCollection config = new NameValueCollection();
+ config.Add("ssl", "false");
+ RedisObjectCache provider = new RedisObjectCache("test", config);
+
+ DateTime utxExpiry = DateTime.UtcNow.AddSeconds(3);
+ provider.Set("key7", "data7", utxExpiry, "testRegion");
+ provider.Remove("key7", "testRegion");
+ object data = provider.Get("key7", "testRegion");
+ Assert.Equal(null, data);
+ }
+ }
+
+ [Fact]
+ public void ExpiryTest()
+ {
+ using (new RedisServer())
+ {
+ NameValueCollection config = new NameValueCollection();
+ config.Add("ssl", "false");
+ RedisObjectCache provider = new RedisObjectCache("test", config);
+
+ DateTime utxExpiry = DateTime.UtcNow.AddSeconds(1);
+ provider.Set("key8", "data8", utxExpiry, "testRegion");
+ // Wait for 1.1 seconds so that data will expire
+ System.Threading.Thread.Sleep(1100);
+ object data = provider.Get("key8", "testRegion");
+ Assert.Equal(null, data);
+ }
+ }
+
+ [Fact]
+ public void AddScriptFixForExpiryTest()
+ {
+ using (new RedisServer())
+ {
+ NameValueCollection config = new NameValueCollection();
+ config.Add("ssl", "false");
+ RedisObjectCache provider = new RedisObjectCache("test", config);
+
+ DateTime utxExpiry = DateTime.UtcNow.AddSeconds(1);
+ provider.Add("key9", "data9", utxExpiry, "testRegion");
+ object data = provider.Get("key9", "testRegion");
+ Assert.Equal("data9", data);
+ // Wait for 1.1 seconds so that data will expire
+ System.Threading.Thread.Sleep(1100);
+ data = provider.Get("key9", "testRegion");
+ Assert.Equal(null, data);
+ }
+ }
+
+ [Fact]
+ public void SlidingExpiryTest()
+ {
+ using (new RedisServer())
+ {
+ NameValueCollection config = new NameValueCollection();
+ config.Add("ssl", "false");
+ RedisObjectCache provider = new RedisObjectCache("test", config);
+
+ CacheItemPolicy policy = new CacheItemPolicy
+ {
+ SlidingExpiration = TimeSpan.FromSeconds(4)
+ };
+
+ provider.Set("key8", "data8", policy, "testRegion");
+ // Wait for 500 seconds, get the data to reset the exiration
+ System.Threading.Thread.Sleep(2000);
+ object data = provider.Get("key8", "testRegion");
+ Assert.Equal("data8", data);
+
+ // 1.1 sec after intial insert, but should still be there.
+ System.Threading.Thread.Sleep(2400);
+ data = provider.Get("key8", "testRegion");
+ Assert.Equal("data8", data);
+
+ // Wait for 1.1 seconds so that data will expire
+ System.Threading.Thread.Sleep(4400);
+ data = provider.Get("key8", "testRegion");
+ Assert.Equal(null, data);
+
+ }
+ }
+
+ [Fact]
+ public void ContainsTest()
+ {
+ using (new RedisServer())
+ {
+ NameValueCollection config = new NameValueCollection();
+ config.Add("ssl", "false");
+ RedisObjectCache provider = new RedisObjectCache("test", config);
+
+ DateTime utxExpiry = DateTime.UtcNow.AddSeconds(1);
+ provider.Set("key10", "data10", utxExpiry, "testRegion");
+ Assert.True(provider.Contains("key10", "testRegion"));
+ }
+ }
+
+ [Fact]
+ public void DoesNotContainsTest()
+ {
+ using (new RedisServer())
+ {
+ NameValueCollection config = new NameValueCollection();
+ config.Add("ssl", "false");
+ RedisObjectCache provider = new RedisObjectCache("test", config);
+
+ DateTime utxExpiry = DateTime.UtcNow.AddMinutes(3);
+ Assert.False(provider.Contains("key10", "testRegion"));
+ }
+ }
+
+ [Fact]
+ public void RegionsTest()
+ {
+ using (new RedisServer())
+ {
+ NameValueCollection config = new NameValueCollection();
+ config.Add("ssl", "false");
+ RedisObjectCache provider = new RedisObjectCache("test", config);
+
+ DateTime utxExpiry = DateTime.UtcNow.AddMinutes(3);
+ provider.Set("key11", "data11.1", utxExpiry, "region1");
+ provider.Set("key11", "data11.2", utxExpiry, "region2");
+
+ object region1Data = provider.Get("key11", "region1");
+ object region2Data = provider.Get("key11", "region2");
+ object regionlessData = provider.Get("key11");
+
+ Assert.Equal("data11.1", region1Data);
+ Assert.Equal("data11.2", region2Data);
+ Assert.Equal(null, regionlessData);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/RedisObjectCache.FunctionalTests/app.config b/test/RedisObjectCache.FunctionalTests/app.config
new file mode 100644
index 00000000..858b34aa
--- /dev/null
+++ b/test/RedisObjectCache.FunctionalTests/app.config
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/RedisObjectCache.FunctionalTests/packages.config b/test/RedisObjectCache.FunctionalTests/packages.config
new file mode 100644
index 00000000..75cc2689
--- /dev/null
+++ b/test/RedisObjectCache.FunctionalTests/packages.config
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/RedisObjectCache.UnitTests/Properties/AssemblyInfo.cs b/test/RedisObjectCache.UnitTests/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..f2dbe462
--- /dev/null
+++ b/test/RedisObjectCache.UnitTests/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("RedisObjectCache.UnitTests")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("RedisObjectCache.UnitTests")]
+[assembly: AssemblyCopyright("Copyright © 2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("c4cf15b8-f965-4192-9008-7244cfaa9a03")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/test/RedisObjectCache.UnitTests/RedisObjectCache.UnitTests.csproj b/test/RedisObjectCache.UnitTests/RedisObjectCache.UnitTests.csproj
new file mode 100644
index 00000000..4a423242
--- /dev/null
+++ b/test/RedisObjectCache.UnitTests/RedisObjectCache.UnitTests.csproj
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+ Debug
+ AnyCPU
+ {C4CF15B8-F965-4192-9008-7244CFAA9A03}
+ Library
+ Properties
+ Microsoft.Web.Redis.UnitTests
+ Microsoft.Web.RedisObjectCache.Unit.Tests
+ v4.5
+ 512
+
+
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\..\packages\FakeItEasy.2.1.0\lib\net40\FakeItEasy.dll
+ True
+
+
+ ..\..\packages\StackExchange.Redis.StrongName.1.2.1\lib\net45\StackExchange.Redis.StrongName.dll
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+ ..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll
+ True
+
+
+ ..\..\packages\xunit.assert.2.1.0\lib\dotnet\xunit.assert.dll
+ True
+
+
+ ..\..\packages\xunit.extensibility.core.2.1.0\lib\dotnet\xunit.core.dll
+ True
+
+
+ ..\..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.dll
+ True
+
+
+
+
+
+
+
+
+
+ Designer
+
+
+
+
+ {63C645E9-906A-4463-9BA7-8CB52206480C}
+ RedisObjectCache
+
+
+
+
+
+
+
+ $(OutputPath)$(TargetFrameworkVersion)\
+ false
+
+
+ $(OutputPath)$(TargetFrameworkVersion)\
+ false
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
+
+
\ No newline at end of file
diff --git a/test/RedisObjectCache.UnitTests/RedisObjectCacheUnitTests.cs b/test/RedisObjectCache.UnitTests/RedisObjectCacheUnitTests.cs
new file mode 100644
index 00000000..2858f5e1
--- /dev/null
+++ b/test/RedisObjectCache.UnitTests/RedisObjectCacheUnitTests.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Specialized;
+using System.Runtime.Caching;
+using FakeItEasy;
+using Xunit;
+
+namespace Microsoft.Web.Redis.UnitTests
+{
+ public class RedisObjectCacheUnitTests
+ {
+ [Fact]
+ public void TryGet()
+ {
+ var fake = A.Fake();
+ A.CallTo(() => fake.Get("key1", null)).Returns(new ArgumentException("foo"));
+ RedisObjectCache cache = new RedisObjectCache("unitTest", new NameValueCollection());
+ cache.cache = fake;
+ var obj = cache.Get("key1");
+ Assert.IsType(obj);
+ }
+
+ [Fact]
+ public void GetWithSlidingExpiration()
+ {
+ var fake = A.Fake();
+ A.CallTo(() => fake.Get("key1", null)).Returns(new SlidingExpiryCacheItem("foo", TimeSpan.FromMinutes(1)));
+ RedisObjectCache cache = new RedisObjectCache("unitTest", new NameValueCollection());
+ cache.cache = fake;
+ var obj = cache.Get("key1");
+ Assert.Equal("foo", obj);
+ A.CallTo(() => fake.ResetExpiry("key1", A.Ignored, null)).MustHaveHappened();
+ }
+
+ [Fact]
+ public void TryAdd()
+ {
+ var fake = A.Fake();
+ DateTime utcExpiry = DateTime.Now;
+ A.CallTo(() => fake.AddOrGetExisting("key1", "object", A.Ignored, null)).Returns(null);
+ RedisObjectCache cache = new RedisObjectCache("unitTest", new NameValueCollection());
+ cache.cache = fake;
+ var obj = cache.Add("key1", "object", utcExpiry);
+ Assert.True(obj);
+ }
+
+ [Fact]
+ public void AddWithSlidingExperation()
+ {
+ var fake = A.Fake();
+ A.CallTo(() => fake.AddOrGetExisting("key1", A.Ignored, A.Ignored, null)).Returns(null);
+ RedisObjectCache cache = new RedisObjectCache("unitTest", new NameValueCollection());
+ cache.cache = fake;
+ var obj = cache.Add("key1", "object", new CacheItemPolicy { SlidingExpiration = TimeSpan.FromMinutes(5) });
+ //Assert.IsType(obj);
+ //Assert.True(obj);
+ A.CallTo(() => fake.AddOrGetExisting("key1", A.That.Matches(s => (string)s.Value == "object"), A.Ignored, null)).MustHaveHappened();
+ }
+
+ [Fact]
+ public void TrySet()
+ {
+ var fake = A.Fake();
+ A.CallTo(() => fake.Set("key1", "object", A.Ignored, null));
+ DateTime utcExpiry = DateTime.Now;
+ RedisObjectCache cache = new RedisObjectCache("unitTest", new NameValueCollection());
+ cache.cache = fake;
+ cache.Set("key1", "object", utcExpiry);
+ A.CallTo(() => fake.Set("key1", "object", A.Ignored, null)).MustHaveHappened();
+ }
+ [Fact]
+ public void TryRemove()
+ {
+ var fake = A.Fake();
+ A.CallTo(() => fake.Remove("key1", null));
+ DateTime utcExpiry = DateTime.Now;
+ RedisObjectCache cache = new RedisObjectCache("unitTest", new NameValueCollection());
+ cache.cache = fake;
+ cache.Remove("key1");
+ A.CallTo(() => fake.Remove("key1", null)).MustHaveHappened();
+ }
+ //[Fact]
+ //public void TryInitialize()
+ //{
+ // var fake = A.Fake();
+ // RedisObjectCache cache = new RedisObjectCache("unitTest", new NameValueCollection());
+ // cache.cache = fake;
+ // NameValueCollection config = new NameValueCollection();
+ // config.Add("host", "localhost");
+ // config.Add("port", "1234");
+ // config.Add("accessKey", "hello world");
+ // config.Add("ssl", "true");
+ // cache.Initialize("name", config);
+
+ // Assert.Equal(RedisOutputCacheProvider.configuration.Host, "localhost");
+ // Assert.Equal(RedisOutputCacheProvider.configuration.Port, 1234);
+ // Assert.Equal(RedisOutputCacheProvider.configuration.AccessKey, "hello world");
+ // Assert.Equal(RedisOutputCacheProvider.configuration.UseSsl, true);
+ //}
+ }
+}
diff --git a/test/RedisObjectCache.UnitTests/app.config b/test/RedisObjectCache.UnitTests/app.config
new file mode 100644
index 00000000..858b34aa
--- /dev/null
+++ b/test/RedisObjectCache.UnitTests/app.config
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/RedisObjectCache.UnitTests/packages.config b/test/RedisObjectCache.UnitTests/packages.config
new file mode 100644
index 00000000..1af27924
--- /dev/null
+++ b/test/RedisObjectCache.UnitTests/packages.config
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/RedisSessionStateProviderUnitTest/RedisSharedConnectionTests.cs b/test/RedisSessionStateProviderUnitTest/RedisSharedConnectionTests.cs
index 3ec3b4c6..60c4993c 100644
--- a/test/RedisSessionStateProviderUnitTest/RedisSharedConnectionTests.cs
+++ b/test/RedisSessionStateProviderUnitTest/RedisSharedConnectionTests.cs
@@ -81,6 +81,11 @@ public ISessionStateItemCollection GetSessionData(object rowDataFromRedis)
return null;
}
+ public bool Exists(string key)
+ {
+ return false;
+ }
+
public void Set(string key, byte[] data, DateTime utcExpiry)
{ }
diff --git a/test/Shared/RedisServer.cs b/test/Shared/RedisServer.cs
index 95e8ddbc..96a70bf0 100644
--- a/test/Shared/RedisServer.cs
+++ b/test/Shared/RedisServer.cs
@@ -3,7 +3,6 @@
// Licensed under the MIT License. See License.txt in the project root for license information.
//
-using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -39,28 +38,37 @@ private void WaitForRedisToStart()
public RedisServer()
{
- KillRedisServers();
- _server = new Process();
- _server.StartInfo.FileName = "..\\..\\..\\..\\..\\..\\packages\\redis-64.3.0.503\\tools\\redis-server.exe";
- _server.StartInfo.Arguments = "--maxmemory 20000000";
- _server.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
- _server.Start();
- WaitForRedisToStart();
+ // only start a redis cache is all instances could be killed
+ if (KillRedisServers())
+ {
+ _server = new Process();
+ _server.StartInfo.FileName = "..\\..\\..\\..\\..\\..\\packages\\redis-64.2.8.17\\redis-server.exe";
+ _server.StartInfo.Arguments = "--maxmemory 20mb";
+ _server.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
+ _server.Start();
+ WaitForRedisToStart();
+ }
}
// Make sure that no redis-server instance is running
- private void KillRedisServers()
+ private bool KillRedisServers()
{
foreach (var proc in Process.GetProcessesByName("redis-server"))
{
+ // redis running as a service, cannot be killed anyway, so lets use it instead
+ if (proc.SessionId != Process.GetCurrentProcess().SessionId)
+ return false;
+
try
{
proc.Kill();
}
catch
{
+ return false;
}
}
+ return true;
}
public void Dispose()