Skip to content

Add flexibility to AddStackExchangeRedisCache by removing setupAction #49721

Closed
@tebeco

Description

@tebeco

Background and Motivation

If you're declaring your own Configuration section like this:

{
  "MyOwnRedis": {
    "DefaultLease": "00:10:00",
    "KeyPrefix": "my-app"
    "ConnectionString": "this is the actual connection string for RedisCacheOptions"
  }
}

and thus doing:

public class MyOwnRedisOptions
{
  public const string SectionName = "MyOwnRedis";

  public TimeSpan DefaultLease { get; set; }
  public string KeyPrefix { get; set; } = default!;
  public string ConnectionString { get; set; } = default!;
}
services
  .AddOptions<MyOwnRedisOptions>
  .BindConfiguration(MyOwnRedisOptions.SectionName)
  .Validate(options => !string.IsNullOrWhiteSpace(options.ConnectionString), $"The configuration key for {MyOwnRedisOptions.SectionName}:{nameof(MyOwnRedisOptions.ConnectionString)} cannot be null or  whitespace.")
  .Validate(options => options...............);
  //...

the moment you're trying to call AddStackExchangeRedisCache you're ending up in a weird situation:

services.AddStackExchangeRedisCache(options =>
{
  option.Connection = builders.Configuration.Get<string>($"{MyOwnRedisOptions.SectionName}:{nameof(MyOwnRedisOptions.ConnectionString)})
};

or
// this has the benefits or still running your Options Validation entiely

services.AddStackExchangeRedisCache(_ => {}) // this is super weird
services
  .AddOptions<RedisCacheOptions>()
  .Configure<IOptions<MyOwnRedisOptions>>((redisCacheOptions, myOwnRedisOptions) =>
  {
    redisCacheOptions.Configuration = myOwnRedisOptions.Value.ConnectionString;
  });
};

Proposed API

  • Add an overload where the setupAction is not required
  • Avoid nullable on setupAction

Please provide the specific public API signature diff that you are proposing. For example:

namespace Microsoft.Extensions.DependencyInjection;

public static class StackExchangeRedisCacheServiceCollectionExtensions
{
+    public static IServiceCollection AddStackExchangeRedisCache(this IServiceCollection services)
+    public static IServiceCollection AddStackExchangeRedisOutputCache(this IServiceCollection services)
}

Usage Examples

services
  .AddOptions<MyOwnRedisOptions>
  .BindConfiguration(MyOwnRedisOptions.SectionName)
  .Validate(options => !string.IsNullOrWhiteSpace(options.ConnectionString), "configuration for redis ConnectionString cannot be null or ...... ")
  .Validate(options => options...............);
  //...

services
  .AddOptions<RedisCacheOptions>()
  .Configure<IOptions<MyOwnRedisOptions>>((redisCacheOptions, myOwnRedisOptions) =>
  {
    redisCacheOptions.Configuration = myOwnRedisOptions.Value.ConnectionString;
  });
};

services.AddStackExchangeRedisCache()

Alternative Designs

Not exactly a fan of using null as optional overload in general. It opens the code to more nullability check or bug tracking NRE like "well I was sure I gave it a value".

PS: Only writing it for AddStackExchangeRedisCache but it would be the the same for the OutputCache as well

namespace Microsoft.Extensions.DependencyInjection;

public static class StackExchangeRedisCacheServiceCollectionExtensions
{
+    public static IServiceCollection AddStackExchangeRedisCache(this IServiceCollection services, Action<RedisCacheOptions> setupAction = null)
{
        ArgumentNullThrowHelper.ThrowIfNull(services);
-       ArgumentNullThrowHelper.ThrowIfNull(setupAction);

        services.AddOptions();
-       services.Configure(setupAction);
+       if(setupAction != null)
+       {
+           services.Configure(setupAction);
+       }

        services.Add(ServiceDescriptor.Singleton<IDistributedCache, RedisCacheImpl>());

        return services;
}

or

namespace Microsoft.Extensions.DependencyInjection;

public static class StackExchangeRedisCacheServiceCollectionExtensions
{
+    public static IServiceCollection AddStackExchangeRedisCache(this IServiceCollection services, Action<RedisCacheOptions> setupAction = null)
{
        ArgumentNullThrowHelper.ThrowIfNull(services);
-       ArgumentNullThrowHelper.ThrowIfNull(setupAction);

        services.AddOptions();
-       services.Configure(setupAction);
+       services.Configure(setupAction ?? _ => {});

        services.Add(ServiceDescriptor.Singleton<IDistributedCache, RedisCacheImpl>());

        return services;
}

Risks

The risk of bad setup/configuration is still owned by inner implementation such as RedisCache / RedisCacheImpl etc ...

Metadata

Metadata

Assignees

Labels

api-suggestionEarly API idea and discussion, it is NOT ready for implementationarea-networkingIncludes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions