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