Skip to content

Add flexibility to AddStackExchangeRedisCache by removing setupAction #49721

@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
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions