Skip to content

Commit

Permalink
Updated RedisTicketStore to pass in configured JsonSerializerOptions
Browse files Browse the repository at this point in the history
  • Loading branch information
melittleman committed Feb 13, 2024
1 parent 7a62d02 commit 07e5bec
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 66 deletions.
20 changes: 14 additions & 6 deletions src/Authentication/RedisTicketStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

/// <inheritdoc />
Expand Down Expand Up @@ -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)
{
Expand All @@ -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<AuthenticationTicket>(GetKey(key));
return Json.GetAsync<AuthenticationTicket>(GetKey(key), serializerOptions: _jsonOptions.Serializer);
}

/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<ITicketStore>(s =>
{
IRedisConnectionProvider provider = s.GetRequiredService<IRedisConnectionProvider>();
IOptionsMonitor<RedisJsonOptions> optionsMonitor = s.GetRequiredService<IOptionsMonitor<RedisJsonOptions>>();
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<CookieAuthenticationOptions>(options.CookieSchemeName).Configure<ITicketStore>((options, store) =>
builder.Services.AddOptions<CookieAuthenticationOptions>(ticketOptions.CookieSchemeName).Configure<ITicketStore>((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());
Expand All @@ -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<RedisJsonOptions>(builder.Name, options =>
{
options.Serializer = jsonSerializer;
});

return builder;
}

internal static IRedisConnectionBuilder ConfigureRedisConnection(
this IRedisConnectionBuilder builder,
Action<RedisConnectionOptions>? configure = null)
Expand Down
30 changes: 0 additions & 30 deletions src/DependencyInjection/Extensions/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,36 +29,6 @@ public static class ServiceCollectionExtensions
return services;
}

/// <summary>
/// Adds an instance of <see cref="RedisJsonOptions"/> to the DI container,
/// based on the provided <see cref="JsonSerializerOptions"/>.
/// </summary>
/// <param name="services">The DI container Services collection.</param>
/// <param name="options">
/// Allows a fully customizable <see cref="JsonSerializerOptions"/>
/// instance to be passed to the Redis JSON serilializer.
/// </param>
/// <returns>
/// The <paramref name="services"/> to be used for further configuration.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="services" /> is null.
/// </exception>
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<RedisJsonOptions>(options =>
{
options.Serializer = jsonSerializer;
});

return services;
}

/// <summary>
/// Adds a named Redis connection to the DI services collection within the singleton <see cref="DefaultRedisConnectionProvider" />.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/DependencyInjection/Options/RedisJsonOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ namespace RedisKit.DependencyInjection.Options;

public sealed record RedisJsonOptions
{
public JsonSerializerOptions Serializer { get; set; } = new JsonSerializerOptions();
public JsonSerializerOptions Serializer { get; } = new();
}
19 changes: 12 additions & 7 deletions src/Querying/Extensions/SearchCommandsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<IPagedList<T>> SearchAsync<T>(
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -117,6 +120,8 @@ public static async Task<ICollection<T>> SearchAllAsync<T>(this SearchCommands s
// page 1, with 100 results per page.
filter ??= new SearchFilter(1, 100);

// TODO: try catch...

IPagedList<T> results = await search.SearchAsync<T>(indexName, "*", filter);

// We must have managed to retrieve all results
Expand Down

0 comments on commit 07e5bec

Please sign in to comment.