Created
September 4, 2018 15:04
-
-
Save lakario/8e10da22ab4117eef688ae8b8b1db50e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
static void Main() | |
{ | |
var builder = new ContainerBuilder(); | |
builder.RegisterType<SomeDependency>().SingleInstance(); | |
builder.RegisterTypeWithCaching<MyApplicationService>().SingleInstance(); | |
var container = builder.Build(); | |
var proxy = container.Resolve<MyApplicationService>(); | |
TestCase1(proxy); | |
TestCase2(proxy); | |
TestCase3(proxy); | |
TestCase1(proxy); | |
TestCase2(proxy); | |
TestCase3(proxy); | |
Console.WriteLine("Sleeping for 1000 ms...\n"); | |
Thread.Sleep(1000); | |
TestCase1(proxy); | |
TestCase2(proxy); | |
TestCase3(proxy); | |
TestCase1(proxy); | |
TestCase2(proxy); | |
TestCase3(proxy); | |
} | |
static void TestCase1(MyApplicationService service) | |
{ | |
Console.Write($"[Cached(15)] {nameof(MyApplicationService)}.{nameof(service.MethodA)}(\"foo\", \"bar\", 50): "); | |
Console.WriteLine(service.MethodA("foo", "bar", 50)); | |
} | |
static void TestCase2(MyApplicationService service) | |
{ | |
Console.Write($"[Cached(15)] {nameof(MyApplicationService)}.{nameof(service.MethodA)}(\"bar\", \"foo\", 50): "); | |
Console.WriteLine(service.MethodA("bar", "foo", 50)); | |
} | |
static void TestCase3(MyApplicationService service) | |
{ | |
Console.Write($"{nameof(MyApplicationService)}.{nameof(service.MethodB)}(): "); | |
Console.Write(service.MethodB() + "\n\n"); | |
} | |
public static class AutofacExtensions | |
{ | |
private static readonly ProxyGenerator _proxyGenerator = new ProxyGenerator(); | |
public static IRegistrationBuilder<T, SimpleActivatorData, SingleRegistrationStyle> | |
RegisterTypeWithCaching<T>(this ContainerBuilder builder) | |
{ | |
return builder.Register(ctx => | |
{ | |
var args = typeof(T) | |
.GetConstructors() | |
.Select(b => b.GetParameters()) | |
.OrderByDescending(b => b.Length) | |
.First() | |
.Select(t => ctx.Resolve(t.ParameterType)).ToArray(); | |
return (T)_proxyGenerator.CreateClassProxy(typeof(T), args, new CachingInterceptor()); | |
}) | |
.As(typeof(T)); | |
} | |
} | |
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property)] | |
public class CachedAttribute : Attribute | |
{ | |
public double? DurationMs { get; set; } | |
public CachedAttribute() | |
{ | |
} | |
public CachedAttribute(double durationMs) | |
{ | |
DurationMs = durationMs; | |
} | |
} | |
public class SomeDependency | |
{ | |
public SomeDependency() | |
{ | |
} | |
} | |
public class MyApplicationService | |
{ | |
private static readonly Random _random = new Random(); | |
public MyApplicationService(SomeDependency dependency) | |
{ | |
} | |
[Cached(15)] | |
public virtual int MethodA(string input1, string input2, int input3) | |
{ | |
return _random.Next(0, input3); | |
} | |
public virtual int MethodB() | |
{ | |
return _random.Next(0, 1000); | |
} | |
} | |
public class CachingInterceptor : IInterceptor | |
{ | |
private readonly ICache _cache = new InMemoryCache(); | |
public void Intercept(IInvocation invocation) | |
{ | |
var cachedAttribute = invocation.Method.GetCustomAttribute<CachedAttribute>(); | |
if (cachedAttribute == null) | |
{ | |
invocation.Proceed(); | |
} | |
else | |
{ | |
var arguments = String.Join(",", invocation.Arguments.Select(a => $"{a.GetType().Name}:{a.GetHashCode()}")); | |
var hashKey = $"{invocation.Method.DeclaringType.FullName}.{invocation.Method.Name}({arguments})"; | |
if (!_cache.ContainsKey(hashKey)) | |
{ | |
invocation.Proceed(); | |
var cacheDuration = cachedAttribute.DurationMs.HasValue | |
? TimeSpan.FromMilliseconds(cachedAttribute.DurationMs.Value) | |
: (TimeSpan?)null; | |
_cache.Add(hashKey, invocation.ReturnValue, cacheDuration); | |
} | |
else | |
{ | |
invocation.ReturnValue = _cache[hashKey]; | |
} | |
} | |
} | |
} | |
// plubming... | |
public interface ICache | |
{ | |
void Add(string key, object value, TimeSpan? duration = null); | |
object Get(string key); | |
object this[string key] { get; } | |
bool ContainsKey(string key); | |
} | |
public class InMemoryCache : ICache | |
{ | |
private readonly Dictionary<string, CacheEntry> _inMemoryCache = new Dictionary<string, CacheEntry>(); | |
public object this[string key] | |
{ | |
get { return Get(key); } | |
} | |
public void Add(string key, object value, TimeSpan? duration = null) | |
{ | |
var cacheEntry = new CacheEntry(value, duration); | |
_inMemoryCache[key] = cacheEntry; | |
} | |
public object Get(string key) | |
{ | |
if (!_inMemoryCache.ContainsKey(key) || _inMemoryCache[key].IsExpired) | |
{ | |
return null; | |
} | |
return _inMemoryCache[key].Value; | |
} | |
public bool ContainsKey(string key) | |
{ | |
return _inMemoryCache.ContainsKey(key) && !_inMemoryCache[key].IsExpired; | |
} | |
private class CacheEntry | |
{ | |
public DateTime CreatedDateTime { get; } = DateTime.UtcNow; | |
public TimeSpan? Duration { get; } | |
public object Value { get; } | |
public bool IsExpired { get { return Duration.HasValue ? (DateTime.UtcNow - CreatedDateTime) > Duration.Value : false; } } | |
public CacheEntry(object value, TimeSpan? duration) | |
{ | |
Value = value; | |
Duration = duration; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment