| File: RedisExtensions.cs | Web Access |
| Project: src\playground\NativeIntegrations\CSharp\Redis\NativeIntegrations.Redis.csproj (NativeIntegrations.Redis) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Aspire.Hosting; using Aspire.Hosting.Native.Authoring; using Aspire.Hosting.Native.Client; using NativeIntegrations.Redis.Authoring; namespace NativeIntegrations.Redis; /// <summary>Adapts generated portable provider calls to the integration's ordinary builder authoring implementation.</summary> public static class RedisExtensions { /// <summary>Materializes the authored Redis container using original delegated native authority.</summary> [AspireExport("addRedis")] public static async Task<RedisResource> AddRedis(Composition composition, string name, DeclaredValue password, CancellationToken cancellationToken) { var builder = DistributedApplicationBuilder.ForIntegration(); var credential = builder.ImportParameter($"{name}-password", password); var cache = builder.AddRedis(name, credential); var native = await builder.MaterializeAsync(composition, cancellationToken).ConfigureAwait(false); return new RedisResource(native.Declaration(cache.Resource), await native.Value(cache.Resource.ConnectionStringExpression).ConfigureAwait(false)); } } /// <summary>Owns portable Redis callbacks; this is deliberately separate from inert authoring data.</summary> public sealed class RedisResource(DeclaredResource declaration, DeclaredValue connection) : NativeIntegrationResource(declaration) { private CancellationTokenSource? _lifetime; /// <summary>Provides original native declaration authority to the generic builder projection.</summary> [AspireExport("redisDeclaration")] public DeclaredResource GetDeclaration() => Declaration; /// <summary>Provides native symbolic connection authority without returning credential payloads.</summary> [AspireExport("redisConnection")] public DeclaredValue GetConnection() => connection; /// <summary>Initializes provider-local state under native lifecycle ownership.</summary> [AspireExport("redisInitialize"), NativeResourceLifecycle("initialize")] public async Task Initialize(ResourceLifecycleScope scope, CancellationToken cancellationToken) { _lifetime = new CancellationTokenSource(); await scope.WriteLifecycleLogAsync("stdout", "Redis integration initialized.", cancellationToken).ConfigureAwait(false); } /// <summary>Observes endpoint allocation with ephemeral native authority.</summary> [AspireExport("redisAllocated"), NativeResourceLifecycle("allocated")] public Task Allocated(ResourceLifecycleScope scope, CancellationToken cancellationToken) => scope.WriteLifecycleLogAsync("stdout", "Redis endpoint allocated.", cancellationToken); /// <summary>Reports readiness after authenticated native health has succeeded.</summary> [AspireExport("redisReady"), NativeResourceLifecycle("ready")] public Task Ready(ResourceLifecycleScope scope, CancellationToken cancellationToken) => scope.WriteLifecycleLogAsync("stdout", "Redis authenticated and ready.", cancellationToken); /// <summary>Executes authenticated SAVE after public revocation, before workload removal.</summary> [AspireExport("redisRetiring"), NativeResourceLifecycle("retiring")] public async Task Retiring(ResourceLifecycleScope scope, CancellationToken cancellationToken) { if (!await scope.RunLifecycleExecutableAsync(new ExecutableWorkloadLaunch { ExecutablePath = Environment.ProcessPath ?? throw new InvalidOperationException("Redis SAVE requires the provider executable."), WorkingDirectory = AppContext.BaseDirectory, Arguments = ["--redis-save"], Environment = [], Endpoints = [] }, new SymbolicLaunchBindings { Environment = [ new() { Name = "REDIS_HOST", ConfigurationKey = "health.env.REDIS_HOST" }, new() { Name = "REDIS_PORT", ConfigurationKey = "health.env.REDIS_PORT" }, new() { Name = "REDIS_PASSWORD", ConfigurationKey = "health.env.REDIS_PASSWORD" } ], Arguments = [] }, cancellationToken).ConfigureAwait(false)) { throw new InvalidOperationException("Redis's final authenticated snapshot failed."); } } /// <summary>Releases provider-local state exactly once after native retirement acknowledges disposal.</summary> public override ValueTask DisposeAsync() { _lifetime?.Cancel(); _lifetime?.Dispose(); _lifetime = null; return ValueTask.CompletedTask; } /// <summary>Retains the low-level provider projection for transport/conformance consumers.</summary> [AspireExport("withDataVolume")] public async Task<RedisResource> WithDataVolume(string name) { await Declaration.WithVolumeAsync(name, "/data", false).ConfigureAwait(false); return this; } /// <summary>Retains the low-level connection projection separately from fluent consumer authoring.</summary> [AspireExport("withReference")] public async Task<DeclaredResource> WithReference(DeclaredResource consumer, string environmentName) { await consumer.WithReferenceAsync(environmentName, connection).ConfigureAwait(false); return consumer; } }