mirror of https://github.com/Squidex/squidex.git
34 changed files with 440 additions and 411 deletions
@ -0,0 +1,91 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Concurrent; |
|||
using System.Threading; |
|||
|
|||
namespace Squidex.Infrastructure.Caching |
|||
{ |
|||
public sealed class AsyncLocalCache : ILocalCache |
|||
{ |
|||
private static readonly AsyncLocal<ConcurrentDictionary<object, object>> Cache = new AsyncLocal<ConcurrentDictionary<object, object>>(); |
|||
private static readonly AsyncLocalCleaner Cleaner; |
|||
|
|||
private sealed class AsyncLocalCleaner : IDisposable |
|||
{ |
|||
private readonly AsyncLocal<ConcurrentDictionary<object, object>> cache; |
|||
|
|||
public AsyncLocalCleaner(AsyncLocal<ConcurrentDictionary<object, object>> cache) |
|||
{ |
|||
this.cache = cache; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
cache.Value = null; |
|||
} |
|||
} |
|||
|
|||
static AsyncLocalCache() |
|||
{ |
|||
Cleaner = new AsyncLocalCleaner(Cache); |
|||
} |
|||
|
|||
public IDisposable StartContext() |
|||
{ |
|||
Cache.Value = new ConcurrentDictionary<object, object>(); |
|||
|
|||
return Cleaner; |
|||
} |
|||
|
|||
public void Add(object key, object value) |
|||
{ |
|||
var cacheKey = GetCacheKey(key); |
|||
|
|||
var cache = Cache.Value; |
|||
|
|||
if (cache != null) |
|||
{ |
|||
cache[cacheKey] = value; |
|||
} |
|||
} |
|||
|
|||
public void Remove(object key) |
|||
{ |
|||
var cacheKey = GetCacheKey(key); |
|||
|
|||
var cache = Cache.Value; |
|||
|
|||
if (cache != null) |
|||
{ |
|||
cache.TryRemove(cacheKey, out var value); |
|||
} |
|||
} |
|||
|
|||
public bool TryGetValue(object key, out object value) |
|||
{ |
|||
var cacheKey = GetCacheKey(key); |
|||
|
|||
var cache = Cache.Value; |
|||
|
|||
if (cache != null) |
|||
{ |
|||
return cache.TryGetValue(cacheKey, out value); |
|||
} |
|||
|
|||
value = null; |
|||
|
|||
return false; |
|||
} |
|||
|
|||
private static string GetCacheKey(object key) |
|||
{ |
|||
return $"CACHE_{key}"; |
|||
} |
|||
} |
|||
} |
|||
@ -1,68 +0,0 @@ |
|||
// ==========================================================================
|
|||
// 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,33 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
using Squidex.Infrastructure.Caching; |
|||
|
|||
namespace Squidex.Infrastructure.Orleans |
|||
{ |
|||
public sealed class LocalCacheFilter : IIncomingGrainCallFilter |
|||
{ |
|||
private readonly ILocalCache localCache; |
|||
|
|||
public LocalCacheFilter(ILocalCache localCache) |
|||
{ |
|||
Guard.NotNull(localCache, nameof(localCache)); |
|||
|
|||
this.localCache = localCache; |
|||
} |
|||
|
|||
public async Task Invoke(IIncomingGrainCallContext context) |
|||
{ |
|||
using (localCache.StartContext()) |
|||
{ |
|||
await context.Invoke(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Caching; |
|||
|
|||
namespace Squidex.Pipeline |
|||
{ |
|||
public sealed class LocalCacheMiddleware : IMiddleware |
|||
{ |
|||
private readonly ILocalCache localCache; |
|||
|
|||
public LocalCacheMiddleware(ILocalCache localCache) |
|||
{ |
|||
Guard.NotNull(localCache, nameof(localCache)); |
|||
|
|||
this.localCache = localCache; |
|||
} |
|||
|
|||
public async Task InvokeAsync(HttpContext context, RequestDelegate next) |
|||
{ |
|||
using (localCache.StartContext()) |
|||
{ |
|||
await next(context); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,127 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Threading.Tasks; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Infrastructure.Caching |
|||
{ |
|||
public class AsyncLocalCacheTests |
|||
{ |
|||
private readonly ILocalCache sut = new AsyncLocalCache(); |
|||
private int called; |
|||
|
|||
[Fact] |
|||
public async Task Should_add_item_to_cache_when_context_exists() |
|||
{ |
|||
using (sut.StartContext()) |
|||
{ |
|||
sut.Add("Key", 1); |
|||
|
|||
await Task.Delay(5); |
|||
|
|||
var found = sut.TryGetValue("Key", out var value); |
|||
|
|||
Assert.True(found); |
|||
Assert.Equal(1, value); |
|||
|
|||
await Task.Delay(5); |
|||
|
|||
sut.Remove("Key"); |
|||
|
|||
var foundAfterRemove = sut.TryGetValue("Key", out value); |
|||
|
|||
Assert.False(foundAfterRemove); |
|||
Assert.Null(value); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_not_add_item_to_cache_when_context_not_exists() |
|||
{ |
|||
sut.Add("Key", 1); |
|||
|
|||
await Task.Delay(5); |
|||
|
|||
var found = sut.TryGetValue("Key", out var value); |
|||
|
|||
Assert.False(found); |
|||
Assert.Null(value); |
|||
|
|||
sut.Remove("Key"); |
|||
|
|||
await Task.Delay(5); |
|||
|
|||
var foundAfterRemove = sut.TryGetValue("Key", out value); |
|||
|
|||
Assert.False(foundAfterRemove); |
|||
Assert.Null(value); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_call_creator_once_when_context_exists() |
|||
{ |
|||
using (sut.StartContext()) |
|||
{ |
|||
var value1 = sut.GetOrCreate("Key", () => ++called); |
|||
|
|||
await Task.Delay(5); |
|||
|
|||
var value2 = sut.GetOrCreate("Key", () => ++called); |
|||
|
|||
Assert.Equal(1, called); |
|||
Assert.Equal(1, value1); |
|||
Assert.Equal(1, value2); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_call_creator_twice_when_context_not_exists() |
|||
{ |
|||
var value1 = sut.GetOrCreate("Key", () => ++called); |
|||
|
|||
await Task.Delay(5); |
|||
|
|||
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() |
|||
{ |
|||
using (sut.StartContext()) |
|||
{ |
|||
var value1 = await sut.GetOrCreateAsync("Key", () => Task.FromResult(++called)); |
|||
|
|||
await Task.Delay(5); |
|||
|
|||
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() |
|||
{ |
|||
var value1 = await sut.GetOrCreateAsync("Key", () => Task.FromResult(++called)); |
|||
|
|||
await Task.Delay(5); |
|||
|
|||
var value2 = await sut.GetOrCreateAsync("Key", () => Task.FromResult(++called)); |
|||
|
|||
Assert.Equal(2, called); |
|||
Assert.Equal(1, value1); |
|||
Assert.Equal(2, value2); |
|||
} |
|||
} |
|||
} |
|||
@ -1,133 +0,0 @@ |
|||
// ==========================================================================
|
|||
// 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 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); |
|||
} |
|||
} |
|||
} |
|||
@ -1,32 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Entities.Contents.Repositories; |
|||
using Squidex.Domain.Apps.Entities.MongoDb.Contents; |
|||
using Squidex.Infrastructure.Migrations; |
|||
|
|||
namespace Migrate_01.Migrations |
|||
{ |
|||
public sealed class DeleteArchiveCollection : IMigration |
|||
{ |
|||
private readonly IContentRepository contentRepository; |
|||
|
|||
public DeleteArchiveCollection(IContentRepository contentRepository) |
|||
{ |
|||
this.contentRepository = contentRepository; |
|||
} |
|||
|
|||
public async Task UpdateAsync() |
|||
{ |
|||
if (contentRepository is MongoContentRepository mongoContentRepository) |
|||
{ |
|||
await mongoContentRepository.DeleteArchiveAsync(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue