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