Skip to content

RedisService

El servicio RedisService proporciona una interfaz y una implementación para gestionar la conexión a un servidor Redis. Su propósito principal es centralizar la gestión de la conexión, el acceso a la base de datos, la suscripción a canales pub/sub y el manejo de eventos de conexión, permitiendo un uso consistente de Redis en la aplicación.

namespace CodeDesignPlus.Net.Redis.Abstractions;
/// <summary>
/// Manages the connection to the Redis cluster.
/// </summary>
public interface IRedis
{
/// <summary>
/// Gets the Redis connection multiplexer.
/// </summary>
IConnectionMultiplexer Connection { get; }
/// <summary>
/// Gets the Redis database.
/// </summary>
IDatabaseAsync Database { get; }
/// <summary>
/// Gets the Redis subscriber for pub/sub scenarios.
/// </summary>
ISubscriber Subscriber { get; }
/// <summary>
/// Gets a value indicating whether any servers are connected.
/// </summary>
bool IsConnected { get; }
/// <summary>
/// Initiates a connection to the provided Redis instance.
/// </summary>
/// <param name="instance">Configuration details of the Redis instance to connect to.</param>
void Initialize(Instance instance);
}

¿Cómo Funciona?


RedisService actúa como un puente entre la aplicación y un servidor Redis. Internamente, utiliza StackExchange.Redis para manejar la conexión. El servicio se inicializa con una configuración de instancia (Instance) que contiene detalles de la conexión, como la cadena de conexión, el uso de SSL/TLS, etc. Al inicializarse, establece la conexión con Redis y proporciona acceso a la base de datos (IDatabaseAsync), al suscriptor (ISubscriber) y al multiplexador de conexión (IConnectionMultiplexer). Además, maneja eventos de la conexión como cambios de configuración y errores, registrando estos eventos mediante un ILogger.

Métodos


RedisService proporciona un único método para inicializar el servicio con la configuración de la instancia de Redis.

Initialize(Instance instance)

Este método inicializa el servicio Redis con la configuración proporcionada en instance. Configura la conexión a Redis usando la cadena de conexión, habilita SSL/TLS si es necesario, y registra eventos de la conexión.

Propiedades


Las propiedades de RedisService proporcionan acceso a los componentes clave de la conexión a Redis, como la conexión, la base de datos y el suscriptor.

Connection

Propiedad que retorna la conexión multiplexada a redis (IConnectionMultiplexer).

Database

Propiedad que retorna la base de datos de redis (IDatabaseAsync).

Subscriber

Propiedad que retorna el subscritor de redis (ISubscriber) para la funcionalidad pub/sub.

IsConnected

Propiedad que retorna un valor booleano indicando si la conexión a redis está activa o no.

Implementación


namespace CodeDesignPlus.Net.Redis.Services;
/// <summary>
/// Provides Redis services.
/// </summary>
public class RedisService : Abstractions.IRedis
{
private readonly ILogger<RedisService> logger;
/// <summary>
/// Gets the Redis connection multiplexer.
/// </summary>
public IConnectionMultiplexer Connection { get; private set; }
/// <summary>
/// Gets the Redis database.
/// </summary>
public IDatabaseAsync Database { get; private set; }
/// <summary>
/// Gets the Redis subscriber.
/// </summary>
public ISubscriber Subscriber { get; private set; }
/// <summary>
/// Gets a value indicating whether the Redis connection is connected.
/// </summary>
public bool IsConnected => this.Connection.IsConnected;
/// <summary>
/// Initializes a new instance of the <see cref="RedisService"/> class.
/// </summary>
/// <param name="logger">The logger instance.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="logger"/> is null.</exception>
public RedisService(ILogger<RedisService> logger)
{
ArgumentNullException.ThrowIfNull(logger);
this.logger = logger;
}
/// <summary>
/// Initializes the Redis service with the specified instance.
/// </summary>
/// <param name="instance">The Redis instance configuration.</param>
public void Initialize(Instance instance)
{
var configuration = instance.CreateConfiguration();
ConfigureSslIfRequired(instance, configuration);
this.Connection = ConnectionMultiplexer.Connect(configuration);
if (this.Connection.IsConnected)
{
this.RegisterEvents();
this.Subscriber = this.Connection.GetSubscriber();
this.Database = this.Connection.GetDatabase();
}
}
/// <summary>
/// Configures SSL if required for the Redis connection.
/// </summary>
/// <param name="instance">The Redis instance configuration.</param>
/// <param name="configuration">The Redis configuration options.</param>
private static void ConfigureSslIfRequired(Instance instance, ConfigurationOptions configuration)
{
if (configuration.Ssl)
{
configuration.CertificateSelection += (_, _, _, _, _) => CertificateSelection(
instance.PasswordCertificate,
instance.Certificate
);
configuration.CertificateValidation += (_, _, chain, sslPolicyErrors) => CertificateValidation(
chain,
sslPolicyErrors,
instance.PasswordCertificate,
instance.Certificate
);
}
}
/// <summary>
/// Selects the certificate for SSL.
/// </summary>
/// <param name="passwordCertificate">The password for the certificate.</param>
/// <param name="certificate">The certificate path.</param>
/// <returns>The selected certificate.</returns>
private static X509Certificate2 CertificateSelection(string passwordCertificate, string certificate)
{
if (!string.IsNullOrEmpty(passwordCertificate))
return X509CertificateLoader.LoadPkcs12FromFile(certificate, passwordCertificate);
else
return X509CertificateLoader.LoadPkcs12FromFile(certificate, null);
}
/// <summary>
/// Validates the SSL certificate.
/// </summary>
/// <param name="chain">The X509 chain.</param>
/// <param name="sslPolicyErrors">The SSL policy errors.</param>
/// <param name="passwordCertificate">The password for the certificate.</param>
/// <param name="certificate">The certificate path.</param>
/// <returns>True if the certificate is valid; otherwise, false.</returns>
private static bool CertificateValidation(X509Chain chain, SslPolicyErrors sslPolicyErrors, string passwordCertificate, string certificate)
{
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
var serverCACertThumbprint = chain.ChainElements[^1].Certificate.Thumbprint;
var clientCertCollection = X509CertificateLoader.LoadPkcs12CollectionFromFile(certificate, passwordCertificate, X509KeyStorageFlags.DefaultKeySet);
var clientCert = clientCertCollection.OfType<X509Certificate2>().FirstOrDefault(cert => cert.HasPrivateKey);
var clientCertChain = new X509Chain();
var chainPolicy = new X509ChainPolicy
{
RevocationMode = X509RevocationMode.NoCheck
};
chainPolicy.ExtraStore.AddRange(clientCertCollection);
clientCertChain.ChainPolicy = chainPolicy;
clientCertChain.Build(clientCert);
var clientCACertThumbprint = clientCertChain.ChainElements[^1].Certificate.Thumbprint;
return serverCACertThumbprint == clientCACertThumbprint;
}
/// <summary>
/// Registers the Redis connection events.
/// </summary>
private void RegisterEvents()
{
this.Connection.ConfigurationChanged += this.ConfigurationChanged;
this.Connection.ConfigurationChangedBroadcast += this.ConfigurationChangedBroadcast;
this.Connection.ConnectionFailed += this.ConnectionFailed;
this.Connection.ConnectionRestored += this.ConnectionRestored;
this.Connection.ErrorMessage += this.ErrorMessage;
this.Connection.HashSlotMoved += this.HashSlotMoved;
this.Connection.InternalError += this.InternalError;
}
/// <summary>
/// Handles the internal error event.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="args">The event arguments.</param>
private void InternalError(object sender, InternalErrorEventArgs args)
{
var data = new
{
args.ConnectionType,
EndPoint = args.EndPoint.ToString(),
args.Origin
};
this.logger.LogCritical(args.Exception, "Internal Error - Data: {Data}", JsonSerializer.Serialize(data));
}
/// <summary>
/// Handles the hash slot moved event.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="args">The event arguments.</param>
private void HashSlotMoved(object sender, HashSlotMovedEventArgs args)
{
var data = new
{
args.HashSlot,
OldEndPoint = args.OldEndPoint.ToString(),
NewEndPoint = args.NewEndPoint.ToString()
};
this.logger.LogWarning("Hash Slot Moved - Data: {Data}", JsonSerializer.Serialize(data));
}
/// <summary>
/// Handles the error message event.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="args">The event arguments.</param>
private void ErrorMessage(object sender, RedisErrorEventArgs args)
{
var data = new
{
EndPoint = args.EndPoint.ToString(),
args.Message
};
this.logger.LogError("Error Message - Data: {Data}", JsonSerializer.Serialize(data));
}
/// <summary>
/// Handles the connection restored event.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="args">The event arguments.</param>
private void ConnectionRestored(object sender, ConnectionFailedEventArgs args)
{
var data = new
{
args.ConnectionType,
EndPoint = args.EndPoint.ToString(),
args.FailureType,
physicalNameConnection = args.ToString()
};
this.logger.LogInformation(args.Exception, "Connection Restored - Data: {Data}", JsonSerializer.Serialize(data));
}
/// <summary>
/// Handles the connection failed event.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="args">The event arguments.</param>
private void ConnectionFailed(object sender, ConnectionFailedEventArgs args)
{
var data = new
{
args.ConnectionType,
EndPoint = args.EndPoint.ToString(),
args.FailureType,
physicalNameConnection = args.ToString()
};
this.logger.LogInformation(args.Exception, "Connection Failed - Data: {Data}", JsonSerializer.Serialize(data));
}
/// <summary>
/// Handles the configuration changed broadcast event.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="args">The event arguments.</param>
private void ConfigurationChangedBroadcast(object sender, EndPointEventArgs args)
{
var data = new
{
EndPoint = args.EndPoint.ToString(),
};
this.logger.LogInformation("Configuration Changed Broadcast - Data: {Data}", JsonSerializer.Serialize(data));
}
/// <summary>
/// Handles the configuration changed event.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="args">The event arguments.</param>
private void ConfigurationChanged(object sender, EndPointEventArgs args)
{
var data = new
{
EndPoint = args.EndPoint.ToString(),
};
this.logger.LogInformation("Configuration Changed - Data: {Data}", JsonSerializer.Serialize(data));
}
}

La implementación de RedisService utiliza la librería StackExchange.Redis para establecer la conexión con Redis. El método Initialize toma una instancia de Instance que contiene la configuración de la conexión y realiza los siguientes pasos:

  1. Creación de ConfigurationOptions: Utiliza el método CreateConfiguration de la instancia Instance para convertir la configuración en un objeto ConfigurationOptions de StackExchange.Redis.
  2. Configuración de SSL/TLS: Si la configuración especifica el uso de SSL/TLS, el método ConfigureSslIfRequired configura los callbacks necesarios para la selección y validación del certificado.
  3. Establecimiento de la Conexión: Utiliza ConnectionMultiplexer.Connect para establecer la conexión a Redis con las opciones configuradas.
  4. Registro de Eventos: Registra los callbacks para los eventos de conexión, configuración y errores de StackExchange.Redis, utilizando ILogger para registrar la información relevante.
  5. Obtención de la Base de Datos y el Suscriptor: Una vez establecida la conexión, obtiene una instancia de la base de datos y del suscriptor, que se exponen a través de las propiedades Database y Subscriber.

El manejo de SSL/TLS incluye la selección y validación del certificado, buscando en los certificados de la máquina los que cumplan con los requisitos de la cadena del certificado.

Ejemplo de Uso


Este ejemplo muestra cómo inicializar y utilizar IRedis en una aplicación de consola a través de IRedisFactory y la inyección de dependencias de .NET.

using CodeDesignPlus.Net.Redis.Abstractions;
using CodeDesignPlus.Net.Redis.Extensions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.Build();
var serviceCollection = new ServiceCollection();
serviceCollection.AddLogging();
serviceCollection.AddRedis(configuration);
var serviceProvider = serviceCollection.BuildServiceProvider();
var factory = serviceProvider.GetRequiredService<IRedisFactory>();
var service = factory.Create(FactoryConst.RedisCore);
var item = new {
Id = 1,
Name = "CodeDesignPlus"
};
await service.Database.StringSetAsync("item", JsonConvert.SerializeObject(item));
var result = await service.Database.StringGetAsync("item");
Console.WriteLine(result);

Conclusiones


RedisService es un componente esencial para la interacción con Redis en la aplicación, proporcionando una capa de abstracción que facilita el uso de la librería StackExchange.Redis, la gestión de conexiones, la configuración de SSL/TLS y el manejo de errores de conexión. Su uso permite centralizar el acceso a Redis en la aplicación y promueve la reutilización del código.

Referencias Adicionales