Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Implementation of ObjectCache using Redis #72

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ csx
; Visual Studio user state
*.user
*.suo
.vs

; VC++/C# debug info files
*.idb
Expand Down
23 changes: 23 additions & 0 deletions Microsoft.CacheProviders.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
15 changes: 15 additions & 0 deletions src/RedisObjectCache/IObjectCacheConnection.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
56 changes: 56 additions & 0 deletions src/RedisObjectCache/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Provided as a down-level stub for the 4.5 AssemblyMetaDataAttribute class.
/// All released assemblies should define [AssemblyMetadata("Serviceable", "True")].
/// </summary>
[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; }
}
}
253 changes: 253 additions & 0 deletions src/RedisObjectCache/RedisObjectCache.cs
Original file line number Diff line number Diff line change
@@ -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<RedisObjectCache> Instance = new Lazy<RedisObjectCache>(() => 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<string, object> GetValues(IEnumerable<string> keys, string regionName = null)
{
throw new NotImplementedException();
}

public override long GetCount(string regionName = null)
{
throw new NotImplementedException();
}

protected override IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
throw new NotImplementedException();
}

public override CacheEntryChangeMonitor CreateCacheEntryChangeMonitor(IEnumerable<string> keys, string regionName = null)
{
throw new NotSupportedException(RedisProviderResource.ChangeMonitorsNotSupported);
}

#endregion
}
}
Loading