From 07e5beca46f8a665f6474a1b7acac65af375d3af Mon Sep 17 00:00:00 2001 From: Matt Lindsay Date: Tue, 13 Feb 2024 10:15:23 +0000 Subject: [PATCH] Updated RedisTicketStore to pass in configured JsonSerializerOptions --- src/Authentication/RedisTicketStore.cs | 20 +++++++---- .../RedisConnectionBuilderExtensions.cs | 34 +++++++------------ .../Extensions/ServiceCollectionExtensions.cs | 30 ---------------- .../Options/RedisJsonOptions.cs | 2 +- .../Extensions/SearchCommandsExtensions.cs | 19 +++++++---- 5 files changed, 39 insertions(+), 66 deletions(-) diff --git a/src/Authentication/RedisTicketStore.cs b/src/Authentication/RedisTicketStore.cs index e33ddae..86961ec 100644 --- a/src/Authentication/RedisTicketStore.cs +++ b/src/Authentication/RedisTicketStore.cs @@ -18,19 +18,27 @@ public sealed record RedisTicketStore : ITicketStore { private const string SessionId = "session_id"; - private readonly RedisAuthenticationTicketOptions _options; + private readonly RedisAuthenticationTicketOptions _ticketOptions; + private readonly RedisJsonOptions _jsonOptions; private readonly IRedisConnection _redis; private IDatabase Db => _redis.Db; private JsonCommands Json => Db.JSON(); - private string KeyPrefix => _options.KeyPrefix; + private string KeyPrefix => _ticketOptions.KeyPrefix; - public RedisTicketStore(IRedisConnection redis, RedisAuthenticationTicketOptions options) + public RedisTicketStore( + IRedisConnection redis, + RedisAuthenticationTicketOptions ticketOptions, + RedisJsonOptions jsonOptions) { _redis = redis ?? throw new ArgumentNullException(nameof(redis)); - _options = options ?? throw new ArgumentNullException(nameof(options)); + _ticketOptions = ticketOptions ?? throw new ArgumentNullException(nameof(ticketOptions)); + + // The RedisJsonOptions 'should' always contain an instance of AuthenticationTicketJsonConverter + // if the .AddRedisTicketStore extension was used, but should we actually be checking this? + _jsonOptions = jsonOptions ?? throw new ArgumentNullException(nameof(jsonOptions)); } /// @@ -64,7 +72,7 @@ public async Task RenewAsync(string key, AuthenticationTicket ticket) string redisKey = GetKey(key); - if (await Json.SetAsync(redisKey, "$", ticket)) + if (await Json.SetAsync(redisKey, "$", ticket, serializerOptions: _jsonOptions.Serializer)) { if (ticket.Properties.ExpiresUtc.HasValue) { @@ -78,7 +86,7 @@ public async Task RenewAsync(string key, AuthenticationTicket ticket) { if (string.IsNullOrEmpty(key)) throw new ArgumentException("Cannot be null or empty.", nameof(key)); - return Json.GetAsync(GetKey(key)); + return Json.GetAsync(GetKey(key), serializerOptions: _jsonOptions.Serializer); } /// diff --git a/src/DependencyInjection/Extensions/RedisConnectionBuilderExtensions.cs b/src/DependencyInjection/Extensions/RedisConnectionBuilderExtensions.cs index ef60370..46cde7c 100644 --- a/src/DependencyInjection/Extensions/RedisConnectionBuilderExtensions.cs +++ b/src/DependencyInjection/Extensions/RedisConnectionBuilderExtensions.cs @@ -1,6 +1,5 @@ -using System.Text.Json; - -using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -94,23 +93,29 @@ public static class RedisConnectionBuilderExtensions { if (builder is null) throw new ArgumentNullException(nameof(builder)); - RedisAuthenticationTicketOptions options = new(); - configure?.Invoke(options); + RedisAuthenticationTicketOptions ticketOptions = new(); + configure?.Invoke(ticketOptions); builder.Services.TryAddTransient(s => { IRedisConnectionProvider provider = s.GetRequiredService(); + IOptionsMonitor optionsMonitor = s.GetRequiredService>(); IRedisConnection connection = provider.GetRequiredConnection(builder.Name); + RedisJsonOptions jsonOptions = optionsMonitor.Get(builder.Name); - return new RedisTicketStore(connection, options); + return new RedisTicketStore(connection, ticketOptions, jsonOptions); }); - builder.Services.AddOptions(options.CookieSchemeName).Configure((options, store) => + builder.Services.AddOptions(ticketOptions.CookieSchemeName).Configure((options, store) => { options.SessionStore = store; }); + // Whilst this isn't technically required because the RedisTicketStore is hard coded + // to use this converter, it seems sensible to include here as a default in case we want + // the user to be able to override in the future. + return builder.ConfigureRedisJson(options => { options.Serializer.Converters.Add(new AuthenticationTicketJsonConverter()); @@ -129,21 +134,6 @@ public static class RedisConnectionBuilderExtensions return builder; } - public static IRedisConnectionBuilder ConfigureRedisJson( - this IRedisConnectionBuilder builder, - JsonSerializerOptions jsonSerializer) - { - if (builder is null) throw new ArgumentNullException(nameof(builder)); - if (jsonSerializer is null) throw new ArgumentNullException(nameof(jsonSerializer)); - - builder.Services.Configure(builder.Name, options => - { - options.Serializer = jsonSerializer; - }); - - return builder; - } - internal static IRedisConnectionBuilder ConfigureRedisConnection( this IRedisConnectionBuilder builder, Action? configure = null) diff --git a/src/DependencyInjection/Extensions/ServiceCollectionExtensions.cs b/src/DependencyInjection/Extensions/ServiceCollectionExtensions.cs index d996856..7409373 100644 --- a/src/DependencyInjection/Extensions/ServiceCollectionExtensions.cs +++ b/src/DependencyInjection/Extensions/ServiceCollectionExtensions.cs @@ -29,36 +29,6 @@ public static class ServiceCollectionExtensions return services; } - /// - /// Adds an instance of to the DI container, - /// based on the provided . - /// - /// The DI container Services collection. - /// - /// Allows a fully customizable - /// instance to be passed to the Redis JSON serilializer. - /// - /// - /// The to be used for further configuration. - /// - /// - /// Thrown when is null. - /// - public static IServiceCollection ConfigureRedisJson( - this IServiceCollection services, - JsonSerializerOptions jsonSerializer) - { - if (services is null) throw new ArgumentNullException(nameof(services)); - if (jsonSerializer is null) throw new ArgumentNullException(nameof(jsonSerializer)); - - services.Configure(options => - { - options.Serializer = jsonSerializer; - }); - - return services; - } - /// /// Adds a named Redis connection to the DI services collection within the singleton . /// diff --git a/src/DependencyInjection/Options/RedisJsonOptions.cs b/src/DependencyInjection/Options/RedisJsonOptions.cs index 927a313..1d30e66 100644 --- a/src/DependencyInjection/Options/RedisJsonOptions.cs +++ b/src/DependencyInjection/Options/RedisJsonOptions.cs @@ -4,5 +4,5 @@ namespace RedisKit.DependencyInjection.Options; public sealed record RedisJsonOptions { - public JsonSerializerOptions Serializer { get; set; } = new JsonSerializerOptions(); + public JsonSerializerOptions Serializer { get; } = new(); } diff --git a/src/Querying/Extensions/SearchCommandsExtensions.cs b/src/Querying/Extensions/SearchCommandsExtensions.cs index 73ba127..c4761f0 100644 --- a/src/Querying/Extensions/SearchCommandsExtensions.cs +++ b/src/Querying/Extensions/SearchCommandsExtensions.cs @@ -31,9 +31,14 @@ public static class SearchCommandsExtensions // force a re-creation (due to schema changes within the document). if (forceRecreate) { - // Note that this returns false when the index doesn't - // exist so we have to assume success regardless. - await search.DropIndexAsync(indexName); + try + { + await search.DropIndexAsync(indexName); + } + catch (RedisServerException rse) when (rse.Message is "Unknown Index name") + { + // Index already doesn't exist... + } } // We can return true here if the Index already exists @@ -49,10 +54,6 @@ public static class SearchCommandsExtensions // Index already exists, another process must have just got there? return true; } - catch (Exception) - { - return false; - } } public static async Task> SearchAsync( @@ -87,6 +88,8 @@ public static class SearchCommandsExtensions { SearchFilter filter = new(1, 1); + // TODO: try catch... + SearchResult result = await search.SearchAsync(indexName, GetPagedQuery(searchTerm, filter)); return result?.Documents is not null @@ -117,6 +120,8 @@ public static async Task> SearchAllAsync(this SearchCommands s // page 1, with 100 results per page. filter ??= new SearchFilter(1, 100); + // TODO: try catch... + IPagedList results = await search.SearchAsync(indexName, "*", filter); // We must have managed to retrieve all results