Skip to content

Commit cb8c7c6

Browse files
Add a .Net Core distributed cache (#29)
This implementation is not standalone: an IDistributedCache implementation has to be provided through an IDistributedCacheFactory. Factories for RedisCache, SqlServerCache, EnyimMemcachedCore, and MemoryDistributedCacheFactory (meant for tests), are provided as separated packages. Fix #28
1 parent 3e08893 commit cb8c7c6

40 files changed

+2355
-58
lines changed

AsyncGenerator.yml

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,33 @@
11
projects:
2+
- filePath: CoreDistributedCache\NHibernate.Caches.CoreDistributedCache\NHibernate.Caches.CoreDistributedCache.csproj
3+
targetFramework: net461
4+
concurrentRun: true
5+
applyChanges: true
6+
analyzation:
7+
methodConversion:
8+
- conversion: Ignore
9+
hasAttributeName: ObsoleteAttribute
10+
callForwarding: true
11+
cancellationTokens:
12+
guards: true
13+
methodParameter:
14+
- anyInterfaceRule: PubliclyExposedType
15+
parameter: Optional
16+
- parameter: Optional
17+
rule: PubliclyExposedType
18+
- parameter: Required
19+
scanMethodBody: true
20+
scanForMissingAsyncMembers:
21+
- all: true
22+
transformation:
23+
configureAwaitArgument: false
24+
localFunctions: true
25+
asyncLock:
26+
type: NHibernate.Util.AsyncLock
27+
methodName: LockAsync
28+
registerPlugin:
29+
- type: AsyncGenerator.Core.Plugins.EmptyRegionRemover
30+
assemblyName: AsyncGenerator.Core
231
- filePath: CoreMemoryCache\NHibernate.Caches.CoreMemoryCache\NHibernate.Caches.CoreMemoryCache.csproj
332
targetFramework: net461
433
concurrentRun: true
@@ -293,6 +322,47 @@
293322
registerPlugin:
294323
- type: AsyncGenerator.Core.Plugins.NUnitAsyncCounterpartsFinder
295324
assemblyName: AsyncGenerator.Core
325+
- filePath: CoreDistributedCache\NHibernate.Caches.CoreDistributedCache.Tests\NHibernate.Caches.CoreDistributedCache.Tests.csproj
326+
targetFramework: net461
327+
concurrentRun: true
328+
applyChanges: true
329+
analyzation:
330+
methodConversion:
331+
- conversion: Ignore
332+
hasAttributeName: OneTimeSetUpAttribute
333+
- conversion: Ignore
334+
hasAttributeName: OneTimeTearDownAttribute
335+
- conversion: Ignore
336+
hasAttributeName: SetUpAttribute
337+
- conversion: Ignore
338+
hasAttributeName: TearDownAttribute
339+
- conversion: Smart
340+
hasAttributeName: TestAttribute
341+
- conversion: Smart
342+
hasAttributeName: TheoryAttribute
343+
preserveReturnType:
344+
- hasAttributeName: TestAttribute
345+
- hasAttributeName: TheoryAttribute
346+
typeConversion:
347+
- conversion: Ignore
348+
hasAttributeName: IgnoreAttribute
349+
- conversion: Partial
350+
hasAttributeName: TestFixtureAttribute
351+
- conversion: Partial
352+
anyBaseTypeRule: HasTestFixtureAttribute
353+
ignoreSearchForMethodReferences:
354+
- hasAttributeName: TheoryAttribute
355+
- hasAttributeName: TestAttribute
356+
cancellationTokens:
357+
withoutCancellationToken:
358+
- hasAttributeName: TestAttribute
359+
- hasAttributeName: TheoryAttribute
360+
scanMethodBody: true
361+
scanForMissingAsyncMembers:
362+
- all: true
363+
registerPlugin:
364+
- type: AsyncGenerator.Core.Plugins.NUnitAsyncCounterpartsFinder
365+
assemblyName: AsyncGenerator.Core
296366
- filePath: CoreMemoryCache\NHibernate.Caches.CoreMemoryCache.Tests\NHibernate.Caches.CoreMemoryCache.Tests.csproj
297367
targetFramework: net461
298368
concurrentRun: true
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
using System;
2+
using System.Reflection;
3+
4+
[assembly: CLSCompliant(true)]
5+
[assembly: AssemblyDelaySign(false)]
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Enyim.Caching;
4+
using Enyim.Caching.Configuration;
5+
using Microsoft.Extensions.Caching.Distributed;
6+
using Microsoft.Extensions.Logging;
7+
using Newtonsoft.Json;
8+
9+
namespace NHibernate.Caches.CoreDistributedCache.Memcached
10+
{
11+
/// <summary>
12+
/// A Memcached distributed cache factory.
13+
/// </summary>
14+
public class MemcachedFactory : IDistributedCacheFactory
15+
{
16+
private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(MemcachedFactory));
17+
private const string _configuration = "configuration";
18+
19+
private readonly IDistributedCache _cache;
20+
21+
/// <summary>
22+
/// Constructor with configuration properties. It supports <c>configuration</c>, which has to be a JSON string
23+
/// structured like the value part of the <c>"enyimMemcached"</c> property in an appsettings.json file.
24+
/// </summary>
25+
/// <param name="properties">The configurations properties.</param>
26+
public MemcachedFactory(IDictionary<string, string> properties) : this()
27+
{
28+
MemcachedClientOptions options;
29+
if (properties != null && properties.TryGetValue(_configuration, out var configuration) && !string.IsNullOrWhiteSpace(configuration))
30+
{
31+
options = JsonConvert.DeserializeObject<MemcachedClientOptions>(configuration);
32+
}
33+
else
34+
{
35+
Log.Warn("No {0} property provided", _configuration);
36+
options = new MemcachedClientOptions();
37+
}
38+
39+
var loggerFactory = new LoggerFactory();
40+
41+
_cache = new MemcachedClient(loggerFactory, new MemcachedClientConfiguration(loggerFactory, options));
42+
}
43+
44+
private MemcachedFactory()
45+
{
46+
Constraints = new CacheConstraints
47+
{
48+
MaxKeySize = 250,
49+
KeySanitizer = SanitizeKey
50+
};
51+
}
52+
53+
// According to https://groups.google.com/forum/#!topic/memcached/Tz1RE0FUbNA,
54+
// memcached key can't contain space, newline, return, tab, vertical tab or form feed.
55+
// Since keys contains entity identifiers which may be anything, purging them all.
56+
private static readonly char[] ForbiddenChar = new [] { ' ', '\n', '\r', '\t', '\v', '\f' };
57+
58+
private static string SanitizeKey(string key)
59+
{
60+
foreach (var forbidden in ForbiddenChar)
61+
{
62+
key = key.Replace(forbidden, '-');
63+
}
64+
return key;
65+
}
66+
67+
/// <inheritdoc />
68+
public CacheConstraints Constraints { get; }
69+
70+
/// <inheritdoc />
71+
[CLSCompliant(false)]
72+
public IDistributedCache BuildCache()
73+
{
74+
return _cache;
75+
}
76+
77+
private class LoggerFactory : Microsoft.Extensions.Logging.ILoggerFactory
78+
{
79+
public void Dispose()
80+
{
81+
}
82+
83+
public ILogger CreateLogger(string categoryName)
84+
{
85+
return new LoggerWrapper(NHibernateLogger.For(categoryName));
86+
}
87+
88+
public void AddProvider(ILoggerProvider provider)
89+
{
90+
}
91+
}
92+
93+
private class LoggerWrapper : ILogger
94+
{
95+
private readonly INHibernateLogger _logger;
96+
97+
public LoggerWrapper(INHibernateLogger logger)
98+
{
99+
_logger = logger;
100+
}
101+
102+
void ILogger.Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception,
103+
Func<TState, Exception, string> formatter)
104+
{
105+
if (!IsEnabled(logLevel))
106+
return;
107+
108+
if (formatter == null)
109+
throw new ArgumentNullException(nameof(formatter));
110+
111+
_logger.Log(
112+
TranslateLevel(logLevel),
113+
new NHibernateLogValues("EventId {0}: {1}", new object[] { eventId, formatter(state, exception) }),
114+
// Avoid double logging of exception by not providing it to the logger, but only to the formatter.
115+
null);
116+
}
117+
118+
public bool IsEnabled(LogLevel logLevel)
119+
=> _logger.IsEnabled(TranslateLevel(logLevel));
120+
121+
public IDisposable BeginScope<TState>(TState state)
122+
=> NoopScope.Instance;
123+
124+
private NHibernateLogLevel TranslateLevel(LogLevel level)
125+
{
126+
switch (level)
127+
{
128+
case LogLevel.None:
129+
return NHibernateLogLevel.None;
130+
case LogLevel.Trace:
131+
return NHibernateLogLevel.Trace;
132+
case LogLevel.Debug:
133+
return NHibernateLogLevel.Debug;
134+
case LogLevel.Information:
135+
return NHibernateLogLevel.Info;
136+
case LogLevel.Warning:
137+
return NHibernateLogLevel.Warn;
138+
case LogLevel.Error:
139+
return NHibernateLogLevel.Error;
140+
case LogLevel.Critical:
141+
return NHibernateLogLevel.Fatal;
142+
}
143+
144+
return NHibernateLogLevel.Trace;
145+
}
146+
147+
private class NoopScope : IDisposable
148+
{
149+
public static readonly NoopScope Instance = new NoopScope();
150+
151+
public void Dispose()
152+
{
153+
}
154+
}
155+
}
156+
}
157+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<Import Project="../../NHibernate.Caches.props" />
3+
<PropertyGroup>
4+
<Product>NHibernate.Caches.CoreDistributedCache.Memcached</Product>
5+
<Title>NHibernate.Caches.CoreDistributedCache.Memcached</Title>
6+
<Description>Memcached cache provider for NHibernate using .Net Core IDistributedCache (EnyimMemcachedCore).</Description>
7+
<!-- Targeting net461 explicitly in order to avoid https://github.com/dotnet/standard/issues/506 for net461 consumers-->
8+
<TargetFrameworks>net461;netstandard2.0</TargetFrameworks>
9+
<SignAssembly>False</SignAssembly>
10+
<GenerateDocumentationFile>true</GenerateDocumentationFile>
11+
<PackageReleaseNotes>* New feature
12+
* #28 - Add a .Net Core DistributedCache</PackageReleaseNotes>
13+
</PropertyGroup>
14+
<PropertyGroup Condition="'$(TargetFramework)' == 'net461'">
15+
<DefineConstants>NETFX;$(DefineConstants)</DefineConstants>
16+
</PropertyGroup>
17+
<ItemGroup>
18+
<None Include="..\..\NHibernate.Caches.snk" Link="NHibernate.snk" />
19+
</ItemGroup>
20+
<ItemGroup>
21+
<ProjectReference Include="..\NHibernate.Caches.CoreDistributedCache\NHibernate.Caches.CoreDistributedCache.csproj" />
22+
</ItemGroup>
23+
<ItemGroup>
24+
<Content Include="../../readme.md">
25+
<PackagePath>./NHibernate.Caches.readme.md</PackagePath>
26+
</Content>
27+
<Content Include="../../LICENSE.txt">
28+
<PackagePath>./NHibernate.Caches.license.txt</PackagePath>
29+
</Content>
30+
</ItemGroup>
31+
<ItemGroup>
32+
<PackageReference Include="EnyimMemcachedCore" Version="2.1.0" />
33+
</ItemGroup>
34+
</Project>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
using System;
2+
using System.Reflection;
3+
4+
[assembly: CLSCompliant(true)]
5+
[assembly: AssemblyDelaySign(false)]

0 commit comments

Comments
 (0)