mirror of https://github.com/Squidex/squidex.git
9 changed files with 366 additions and 73 deletions
@ -0,0 +1,68 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Microsoft.AspNetCore.Http; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Caching |
||||
|
{ |
||||
|
public sealed class HttpRequestCache : IRequestCache |
||||
|
{ |
||||
|
private readonly IHttpContextAccessor httpContextAccessor; |
||||
|
|
||||
|
public HttpRequestCache(IHttpContextAccessor httpContextAccessor) |
||||
|
{ |
||||
|
Guard.NotNull(httpContextAccessor, nameof(httpContextAccessor)); |
||||
|
|
||||
|
this.httpContextAccessor = httpContextAccessor; |
||||
|
} |
||||
|
|
||||
|
public void Add(object key, object value) |
||||
|
{ |
||||
|
var cacheKey = GetCacheKey(key); |
||||
|
|
||||
|
var items = httpContextAccessor.HttpContext?.Items; |
||||
|
|
||||
|
if (items != null) |
||||
|
{ |
||||
|
items[cacheKey] = value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void Remove(object key) |
||||
|
{ |
||||
|
var cacheKey = GetCacheKey(key); |
||||
|
|
||||
|
var items = httpContextAccessor.HttpContext?.Items; |
||||
|
|
||||
|
if (items != null) |
||||
|
{ |
||||
|
items?.Remove(cacheKey); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public bool TryGetValue(object key, out object value) |
||||
|
{ |
||||
|
var cacheKey = GetCacheKey(key); |
||||
|
|
||||
|
var items = httpContextAccessor.HttpContext?.Items; |
||||
|
|
||||
|
if (items != null) |
||||
|
{ |
||||
|
return items.TryGetValue(cacheKey, out value); |
||||
|
} |
||||
|
|
||||
|
value = null; |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
private static string GetCacheKey(object key) |
||||
|
{ |
||||
|
return $"CACHE_{key}"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Infrastructure.Caching |
||||
|
{ |
||||
|
public interface IRequestCache |
||||
|
{ |
||||
|
void Add(object key, object value); |
||||
|
|
||||
|
void Remove(object key); |
||||
|
|
||||
|
bool TryGetValue(object key, out object value); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Caching |
||||
|
{ |
||||
|
public static class RequestCacheExtensions |
||||
|
{ |
||||
|
public static async Task<T> GetOrCreateAsync<T>(this IRequestCache cache, object key, Func<Task<T>> task) |
||||
|
{ |
||||
|
if (cache.TryGetValue(key, out var value) && value is T typedValue) |
||||
|
{ |
||||
|
return typedValue; |
||||
|
} |
||||
|
|
||||
|
typedValue = await task(); |
||||
|
|
||||
|
cache.Add(key, typedValue); |
||||
|
|
||||
|
return typedValue; |
||||
|
} |
||||
|
|
||||
|
public static T GetOrCreate<T>(this IRequestCache cache, object key, Func<T> task) |
||||
|
{ |
||||
|
if (cache.TryGetValue(key, out var value) && value is T typedValue) |
||||
|
{ |
||||
|
return typedValue; |
||||
|
} |
||||
|
|
||||
|
typedValue = task(); |
||||
|
|
||||
|
cache.Add(key, typedValue); |
||||
|
|
||||
|
return typedValue; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,133 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using FakeItEasy; |
||||
|
using Microsoft.AspNetCore.Http; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Caching |
||||
|
{ |
||||
|
public sealed class HttpRequestCacheTests |
||||
|
{ |
||||
|
private readonly IHttpContextAccessor httpContextAccessor = A.Fake<IHttpContextAccessor>(); |
||||
|
private readonly IRequestCache sut; |
||||
|
private int called; |
||||
|
|
||||
|
public HttpRequestCacheTests() |
||||
|
{ |
||||
|
sut = new HttpRequestCache(httpContextAccessor); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_add_item_to_cache_when_context_exists() |
||||
|
{ |
||||
|
SetupContext(); |
||||
|
|
||||
|
sut.Add("Key", 1); |
||||
|
|
||||
|
var found = sut.TryGetValue("Key", out var value); |
||||
|
|
||||
|
Assert.True(found); |
||||
|
Assert.Equal(1, value); |
||||
|
|
||||
|
sut.Remove("Key"); |
||||
|
|
||||
|
var foundAfterRemove = sut.TryGetValue("Key", out value); |
||||
|
|
||||
|
Assert.False(foundAfterRemove); |
||||
|
Assert.Null(value); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_not_add_item_to_cache_when_context_not_exists() |
||||
|
{ |
||||
|
SetupNoContext(); |
||||
|
|
||||
|
sut.Add("Key", 1); |
||||
|
|
||||
|
var found = sut.TryGetValue("Key", out var value); |
||||
|
|
||||
|
Assert.False(found); |
||||
|
Assert.Null(value); |
||||
|
|
||||
|
sut.Remove("Key"); |
||||
|
|
||||
|
var foundAfterRemove = sut.TryGetValue("Key", out value); |
||||
|
|
||||
|
Assert.False(foundAfterRemove); |
||||
|
Assert.Null(value); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_call_creator_once_when_context_exists() |
||||
|
{ |
||||
|
SetupContext(); |
||||
|
|
||||
|
var value1 = sut.GetOrCreate("Key", () => ++called); |
||||
|
var value2 = sut.GetOrCreate("Key", () => ++called); |
||||
|
|
||||
|
Assert.Equal(1, called); |
||||
|
Assert.Equal(1, value1); |
||||
|
Assert.Equal(1, value2); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_call_creator_twice_when_context_not_exists() |
||||
|
{ |
||||
|
SetupNoContext(); |
||||
|
|
||||
|
var value1 = sut.GetOrCreate("Key", () => ++called); |
||||
|
var value2 = sut.GetOrCreate("Key", () => ++called); |
||||
|
|
||||
|
Assert.Equal(2, called); |
||||
|
Assert.Equal(1, value1); |
||||
|
Assert.Equal(2, value2); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_call_async_creator_once_when_context_exists() |
||||
|
{ |
||||
|
SetupContext(); |
||||
|
|
||||
|
var value1 = await sut.GetOrCreateAsync("Key", () => Task.FromResult(++called)); |
||||
|
var value2 = await sut.GetOrCreateAsync("Key", () => Task.FromResult(++called)); |
||||
|
|
||||
|
Assert.Equal(1, called); |
||||
|
Assert.Equal(1, value1); |
||||
|
Assert.Equal(1, value2); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_call_async_creator_twice_when_context_not_exists() |
||||
|
{ |
||||
|
SetupNoContext(); |
||||
|
|
||||
|
var value1 = await sut.GetOrCreateAsync("Key", () => Task.FromResult(++called)); |
||||
|
var value2 = await sut.GetOrCreateAsync("Key", () => Task.FromResult(++called)); |
||||
|
|
||||
|
Assert.Equal(2, called); |
||||
|
Assert.Equal(1, value1); |
||||
|
Assert.Equal(2, value2); |
||||
|
} |
||||
|
|
||||
|
private void SetupNoContext() |
||||
|
{ |
||||
|
A.CallTo(() => httpContextAccessor.HttpContext).Returns(null); |
||||
|
} |
||||
|
|
||||
|
private void SetupContext() |
||||
|
{ |
||||
|
var httpItems = new Dictionary<object, object>(); |
||||
|
var httpContext = A.Fake<HttpContext>(); |
||||
|
|
||||
|
A.CallTo(() => httpContext.Items).Returns(httpItems); |
||||
|
A.CallTo(() => httpContextAccessor.HttpContext).Returns(httpContext); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue