Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions Dan.Core.UnitTest/AvailableEvidenceCodesServiceTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ public class AvailableEvidenceCodesServiceTest
private readonly ILoggerFactory _loggerFactory = new NullLoggerFactory();
private readonly Mock<IHttpClientFactory> _mockHttpClientFactory = new Mock<IHttpClientFactory>();
private readonly Mock<AsyncPolicy<List<EvidenceCode>>> _mockAsyncPolicy = new Mock<AsyncPolicy<List<EvidenceCode>>>();
private readonly Mock<IDistributedCache> _mockDistributedCache = new Mock<IDistributedCache>();
private readonly Mock<IServiceContextService> _mockServiceContextService = new Mock<IServiceContextService>();
private readonly Mock<IFunctionContextAccessor> _mockFunctionContextAccessor = new Mock<IFunctionContextAccessor>();

Expand Down Expand Up @@ -136,7 +135,6 @@ public async Task CheckServiceContextRequirementsIncluded()
_loggerFactory,
_mockHttpClientFactory.Object,
_policyRegistry,
_mockDistributedCache.Object,
_mockServiceContextService.Object,
_mockFunctionContextAccessor.Object);

Expand Down Expand Up @@ -185,7 +183,6 @@ public async Task GetAliases()
_loggerFactory,
_mockHttpClientFactory.Object,
_policyRegistry,
_mockDistributedCache.Object,
_mockServiceContextService.Object,
_mockFunctionContextAccessor.Object);

Expand Down
59 changes: 15 additions & 44 deletions Dan.Core/Services/AvailableEvidenceCodesService.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Net.Http.Headers;
using Dan.Common.Models;
using Dan.Common.Models;
using Dan.Core.Config;
using Dan.Core.Extensions;
using Dan.Core.Services.Interfaces;
Expand All @@ -12,48 +11,31 @@
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
using AsyncKeyedLock;
using Azure.Identity;

namespace Dan.Core.Services;

public class AvailableEvidenceCodesService : IAvailableEvidenceCodesService
public class AvailableEvidenceCodesService(
ILoggerFactory loggerFactory,
IHttpClientFactory httpClientFactory,
IPolicyRegistry<string> policyRegistry,
IServiceContextService serviceContextService,
IFunctionContextAccessor functionContextAccessor)
: IAvailableEvidenceCodesService
{
public static TimeSpan DistributedCacheTtl = TimeSpan.FromHours(12);
private readonly ILogger<IAvailableEvidenceCodesService> _logger = loggerFactory.CreateLogger<AvailableEvidenceCodesService>();

private readonly ILogger<IAvailableEvidenceCodesService> _logger;
private readonly IHttpClientFactory _httpClientFactory;
private readonly IPolicyRegistry<string> _policyRegistry;
private readonly IDistributedCache _distributedCache;
private readonly IServiceContextService _serviceContextService;

private List<EvidenceCode> _memoryCache = new();
private List<EvidenceCode> _memoryCache = [];
private DateTime _updateMemoryCache = DateTime.MinValue;
private readonly AsyncNonKeyedLocker _semaphoreForceRefresh = new(1);
private readonly AsyncNonKeyedLocker _semaphore = new(1);
private readonly IFunctionContextAccessor _functionContextAccessor;
private const int MemoryCacheTtlSeconds = 120;
private const int MemoryCacheTtlSeconds = 600;

private const string CachingPolicy = "EvidenceCodesCachePolicy";
private const string HttpClientName = "EvidenceCodesClient";
private const string CacheContextKey = "AvailableEvidenceCodes";
private const string CacheResponseHeader = "x-cache";

public AvailableEvidenceCodesService(
ILoggerFactory loggerFactory,
IHttpClientFactory httpClientFactory,
IPolicyRegistry<string> policyRegistry,
IDistributedCache distributedCache,
IServiceContextService serviceContextService,
IFunctionContextAccessor functionContextAccessor)
{
_logger = loggerFactory.CreateLogger<AvailableEvidenceCodesService>();
_httpClientFactory = httpClientFactory;
_policyRegistry = policyRegistry;
_distributedCache = distributedCache;
_serviceContextService = serviceContextService;
_functionContextAccessor = functionContextAccessor;
}

/// <summary>
/// Gets the list of current active evidence codes. This endpoint can be hit several times during a request. In order to reduce I/O to the distributed cache, it employs
/// an additional layer of caching via memory. Uses semaphores to handle concurrent writes to the caches.
Expand Down Expand Up @@ -116,7 +98,7 @@ public Dictionary<string, string> GetAliases()

private void SetCacheDiagnosticsHeader(string value, bool overwrite = false)
{
var requestContextService = _functionContextAccessor.FunctionContext?.InstanceServices.GetService<IRequestContextService>();
var requestContextService = functionContextAccessor.FunctionContext?.InstanceServices.GetService<IRequestContextService>();
if (requestContextService == null) return;
if (overwrite)
{
Expand Down Expand Up @@ -149,25 +131,14 @@ private async Task RefreshEvidenceCodesCache()
es.AuthorizationRequirements.ForEach(x => x.RequirementType = x.GetType().Name);
}

await _distributedCache.SetAsync(CacheContextKey, Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(
evidenceCodes,
new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
})),
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = DistributedCacheTtl
});

_memoryCache = evidenceCodes;
_updateMemoryCache = DateTime.UtcNow.AddSeconds(MemoryCacheTtlSeconds);
}

private async Task<List<EvidenceCode>> GetAvailableEvidenceCodesFromDistributedCache()
{
SetCacheDiagnosticsHeader("hit-distributed");
var cachePolicy = _policyRegistry.Get<AsyncPolicy<List<EvidenceCode>>>(CachingPolicy);
var cachePolicy = policyRegistry.Get<AsyncPolicy<List<EvidenceCode>>>(CachingPolicy);
return await cachePolicy.ExecuteAsync(
async _ => await GetAvailableEvidenceCodesFromEvidenceSources(), new Context(CacheContextKey));
}
Expand All @@ -188,7 +159,7 @@ private async Task<List<EvidenceCode>> GetAvailableEvidenceCodesFromEvidenceSour

private async Task AddServiceContextAuthorizationRequirements(List<EvidenceCode> evidenceCodes)
{
var serviceContexts = await _serviceContextService.GetRegisteredServiceContexts();
var serviceContexts = await serviceContextService.GetRegisteredServiceContexts();
foreach (var serviceContext in serviceContexts)
{
if (!serviceContext.AuthorizationRequirements.Any()) continue;
Expand All @@ -206,7 +177,7 @@ private async Task AddServiceContextAuthorizationRequirements(List<EvidenceCode>

private async Task<List<EvidenceCode>> GetEvidenceCodesFromSource(EvidenceSource source)
{
var client = _httpClientFactory.CreateClient(HttpClientName);
var client = httpClientFactory.CreateClient(HttpClientName);
try
{
var request = new HttpRequestMessage(HttpMethod.Get, source.Url);
Expand Down
Loading