Optiize binder and api key retrieval caching

This commit is contained in:
honzapatCZ 2026-09-12 14:20:33 +02:00
parent c5a9db661b
commit bc4b04e378
2 changed files with 62 additions and 37 deletions

View File

@ -90,31 +90,27 @@ namespace NejCommon.Models
//get the binding parameter attributes
// Fetch the route values from the route data
var capturedRouteData = new Dictionary<string, object>();
foreach (var routeValue in _routeValues)
var values = new string[_routeValues.Length];
for (var i = 0; i < _routeValues.Length; i++)
{
var name = routeValue.Name;
if (routeValue.Primary && !string.IsNullOrWhiteSpace(bindingContext.ModelName))
{
name = bindingContext.ModelName;
}
var routeValue = _routeValues[i];
/*
Console.WriteLine("Route value: " + name);
Console.WriteLine();
Console.WriteLine(bindingContext.ActionContext.RouteData.Values[name]);
*/
var name =
routeValue.Primary && !string.IsNullOrWhiteSpace(bindingContext.ModelName)
? bindingContext.ModelName
: routeValue.Name;
var value = bindingContext.ValueProvider.GetValue(name).FirstOrDefault();//bindingContext.ActionContext.RouteData.Values[name];
if (value is null)
// If these really are route values, don't go through the entire
// MVC ValueProvider infrastructure.
if (!bindingContext.ActionContext.RouteData.Values.TryGetValue(name, out var rawValue) ||
rawValue is null)
{
bindingContext.ModelState.AddModelError(name, "Route value not found");
bindingContext.Result = ModelBindingResult.Failed();
return;
}
else
{
capturedRouteData[routeValue.Name] = value;
}
values[i] = rawValue.ToString()!;
}
// Check if all the required route values are present
@ -124,21 +120,39 @@ namespace NejCommon.Models
return;
}
var entityParameter = Expression.Parameter(typeof(TEntity), "entity");
// Find the entity with the specified route values
var query = _db.Set<TEntity>().AsQueryable();
foreach (var routeValue in _routeValues)
Expression? predicateBody = null;
for (var i = 0; i < _routeValues.Length; i++)
{
var value = capturedRouteData[routeValue.Name];
var routeValue = _routeValues[i];
var par = Expression.Parameter(typeof(TEntity));
var exp = Expression.Invoke(routeValue.Expression, par, Expression.Constant((string)value));
var redExp = exp.Reduce();
var compiledExp = Expression.Lambda<Func<TEntity, bool>>(redExp, par);
// IMPORTANT:
// captured variable -> EF parameter
// rather than Expression.Constant(value)
var value = values[i];
query = query.Where(compiledExp);
Expression<Func<string>> valueExpression = () => value;
var expression = Expression.Invoke(
routeValue.Expression,
entityParameter,
valueExpression.Body);
var body = expression.Reduce();
predicateBody = predicateBody is null
? body
: Expression.AndAlso(predicateBody, body);
}
var model = await query.FirstOrDefaultAsync(cancellationToken);
var predicate =
Expression.Lambda<Func<TEntity, bool>>(
predicateBody!,
entityParameter);
var model = await _db.Set<TEntity>().AsQueryable().FirstOrDefaultAsync(predicate, cancellationToken);
if (model == null)
{

View File

@ -1,17 +1,23 @@
using AspNetCore.Authentication.ApiKey;
using Microsoft.EntityFrameworkCore;
using NejCommon.Models;
using NejCommon.Utils;
using ZiggyCreatures.Caching.Fusion;
namespace NejCommon.Services;
public partial class ApiKeyProvider : IApiKeyProvider, IScopedService
{
private readonly ILogger<ApiKeyProvider> _logger;
private readonly IFusionCache _cache;
public async Task<IApiKey> ProvideAsync(string key)
public async Task<IApiKey?> ProvideAsync(string key)
{
try
using var activity = Telemetry.Start("ApiKeyProvider.ProvideAsync");
return await _cache.GetOrSetAsync($"ApiKeyProvider.ProvideAsync.{key}", async entry =>
{
// write your validation implementation here and return an instance of a valid ApiKey or retun null for an invalid key.
var lookup = ApiKey.ComputeLookupHash(key);
var apiKey = await GetApiKeySet().FirstOrDefaultAsync(x => x.LookupHash.SequenceEqual(lookup));
@ -25,17 +31,22 @@ public partial class ApiKeyProvider : IApiKeyProvider, IScopedService
return null;
}
if (apiKey.Revoked)
{
return null;
}
apiKey.Key = key;
// write your validation implementation here and return an instance of a valid ApiKey or retun null for an invalid key.
// return await _apiKeyRepository.GetApiKeyAsync(key);
return apiKey;
}
catch (System.Exception exception)
}, new FusionCacheEntryOptions()
{
_logger.LogError(exception, exception.Message);
throw;
}
Duration = TimeSpan.FromMinutes(1),
IsFailSafeEnabled = true,
FailSafeMaxDuration = TimeSpan.FromMinutes(5),
});
}
public static string GenerateKey() => "nej-" + NanoidDotNet.Nanoid.Generate(size: 256);