Almacenamiento S3

DinaS3ClientC opera sobre un bucket S3 o MinIO y DinaS3MultiClientC replica, repara y detecta deriva entre varios buckets desde .NET.

El módulo S3 del paquete Dinaup se conecta a cualquier almacén compatible con S3 (AWS S3, MinIO, Cloudflare R2, OVH) sobre el cliente de Minio. Trae dos clientes en el namespace Dinaup.S3: DinaS3ClientC para un bucket y DinaS3MultiClientC para replicar y reparar sobre varios.

Antes de empezar

  • Paquete Dinaup instalado. Ver SDK .NET.
  • Clave de acceso, clave secreta, endpoint y nombre del bucket. Guárdalos en el Vault, no en el código.
using Dinaup.S3;

Un bucket: DinaS3ClientC

Conectar

var s3 = new DinaS3ClientC(
    _s3Token:    "ACCESS_KEY",
    _s3Secret:   "SECRET_KEY",
    _s3EndPoint: "s3.tu-dominio.com",
    _Bucket:     "mi-bucket",
    _BaseDir:    "app/");   // prefijo opcional dentro del bucket

// Por cadena de conexión
var s3b = new DinaS3ClientC("s3://ACCESS:SECRET@s3.tu-dominio.com/mi-bucket/app");

// Por diccionario: accessKey, secretKey, endpoint, bucketName, prefix, region
var s3c = new DinaS3ClientC(parametros);
ConstructorDetalle
New(_s3Token, _s3Secret, _s3EndPoint, _Bucket, _BaseDir = "", _regionForazada = "")Parámetros sueltos. _regionForazada salta la detección de región por proveedor.
New(_connectionString, _regionForazada = "")s3://ACCESS:SECRET@host[:puerto]/bucket[/prefijo]. Con http:// delante desactiva TLS; por defecto usa TLS.
New(param As Dictionary(Of String, String))Claves accessKey, secretKey, endpoint, bucketName, prefix, region.

La conexión se abre en la primera operación (Iniciar()). La región se deduce del endpoint para R2 (auto) y OVH. EntrarDir(dir) añade un subdirectorio al prefijo.

Operar con objetos

Las rutas son relativas al bucket y al prefijo.

// Subir bytes
await s3.BytesUploadAsync("informes/2026-07.json", contenidoBytes);

// Descargar bytes
byte[] datos = await s3.BytesReadAsync("informes/2026-07.json");

// Comprobar si existe (clave exacta, no prefijo)
bool hay = await s3.FileExistAsync("informes/2026-07.json");

// Subir y descargar archivos de disco
await s3.FileUploadAsync("informes/2026-07.pdf", @"C:\temp\informe.pdf");
await s3.FileDownloadAsync("informes/2026-07.pdf", @"C:\temp\bajado.pdf");

// Sube solo si el contenido difiere del que ya hay
await s3.FileUploadIfIsDifferentAsync("logo.png", @"C:\assets\logo.png");

// Listar
var items = await s3.FileList("informes/", recursive: true);

// Borrar
await s3.FileRemoveAsync("informes/2026-07.json");
MétodoQué hace
BytesUploadAsync(ruta, bytes)Sube bytes. Si el PUT falla, lo registra en el log y no lanza.
BytesUploadThrowAsync(ruta, bytes)Igual, pero propaga la excepción del PUT.
StreamUploadAsync(ruta, stream)Sube desde un Stream o MemoryStream.
BytesReadAsync(ruta)Descarga el objeto como bytes.
FileUploadAsync(ruta, archivoLocal) / FileDownloadAsync(ruta, destinoLocal)Sube o descarga un archivo de disco.
FileUploadIfIsDifferentAsync(ruta, archivoLocal)Sube solo si el contenido cambió.
FileExistAsync(ruta)true si existe ese objeto.
FileRemoveAsync(ruta)Borra el objeto.
LastModified(ruta)Fecha de última modificación.
GetFileMetadataAsync(ruta)ObjectStat de Minio con tamaño, ETag y metadatos.
FileList(prefijo, recursive = true, limit = -1)Lista objetos bajo un prefijo.
DirList(prefijo, recursive = true)Lista directorios.
ListAll(prefijo, files = true, directories = false, recursive = true) / ListAll_GetEnumeratorLista archivos y directorios; la variante enumerador no carga todo en memoria.
JObjectWriteAsync<T>(ruta, objeto) / JObjectReadAsync<T>(ruta)Serializa y deserializa JSON.
NoSQL_WriteAsync(ruta, texto) / NoSQL_ReadAsync(ruta) / NoSQL_RemoveAsync(ruta)Texto plano como clave-valor.

Los contadores TotalRead, TotalWrite, TotalList, TotalDelete, TotalStat, TotalFileUpload y TotalFileDownload cuentan intentos por operación; ResetCounters() los pone a cero.

URLs firmadas

Genera URLs temporales de lectura o de subida sin exponer las claves. Caducan a los 600 segundos si no indicas otro valor.

// Lectura
string urlLectura = s3.SignGet("informes/2026-07.pdf", expirationSeconds: 600);

// Lectura con nombre de descarga y sin cabecera Content-Disposition
string urlInline = s3.SignGet("informes/2026-07.pdf", NombreArchivo: "informe.pdf", disposition: false);

// Escritura directa desde el navegador
string urlSubida = s3.SignPut("subidas/nuevo.pdf", expirationSeconds: 600);

Salud

bool ok = await s3.HealthCheckAsync();

var salud = await s3.SaludAsync();
// salud.Estado: Ok, SoloLectura, Credenciales, Bucket, Red, SinConfigurar o Desconocido
// salud.Motivo: el texto del fallo; salud.Ok: true solo con Estado Ok

SaludAsync lee el marcador HealthCheck.dat y, si no existe, lo escribe: distingue una clave de solo lectura (estado SoloLectura) de un bucket inaccesible. Ping() escribe y relee una clave de prueba. DinaS3ClientC implementa IHealthCheck: registrado con AddHealthChecks().AddCheck("s3", s3), devuelve Healthy, Degraded (solo lectura) o Unhealthy.

Varios buckets: DinaS3MultiClientC

Envuelve una lista de DinaS3ClientC y opera sobre todos: escribe en todos y lee de la réplica con la versión más reciente. Si a un bucket le falta el objeto o lo tiene atrasado, lo repara desde la réplica buena antes de devolver.

var multi = new DinaS3MultiClientC(
    new List<DinaS3ClientC> { s3Primario, s3Secundario },
    _autoRepair: true);

// Escribe en todos los buckets
var res = await multi.WriteBytesAsync("informes/2026-07.json", datos);
if (res.AllOk == false)
    Console.WriteLine($"Fallaron: {string.Join(", ", res.FailedHosts)}");

// Lee de la réplica más reciente
byte[] leido = await multi.ReadBytesAsync("informes/2026-07.json");

// Objetos serializados en JSON y texto
await multi.WriteObjectAsync("config/app.json", miConfig);
var config = await multi.ReadObjectAsync<AppConfig>("config/app.json");
string texto = await multi.ReadStringAsync("config/version.txt");

// Existencia, listado, archivos y borrado
bool hay = await multi.ExistsAsync("informes/2026-07.json");
var items = await multi.ListAsync("informes/", recursive: true);
await multi.UploadFileAsync("informes/2026-07.pdf", @"C:\temp\informe.pdf");
await multi.DownloadFileAsync(@"C:\temp\bajado.pdf", "informes/2026-07.pdf");
bool borrado = await multi.DeleteAsync("informes/2026-07.json");
MétodoQué hace
WriteBytesAsync(ruta, bytes) / WriteObjectAsync<T>(ruta, objeto)Escribe en todas las réplicas en paralelo. Devuelve WriteResult con OkHosts, FailedHosts, AllOk y AnyOk.
ReadBytesAsync(ruta) / ReadObjectAsync<T>(ruta) / ReadStringAsync(ruta)Lee de la réplica más reciente. Con AutoRepair, repara las atrasadas antes de devolver. null si ninguna tiene el objeto.
ExistsAsync(ruta)true si al menos una réplica que responde lo tiene.
ListAsync(prefijo, recursive = true, limit = -1)Unión de todas las réplicas, sin duplicados por clave. Una réplica caída se ignora.
UploadFileAsync(ruta, archivoLocal) / DownloadFileAsync(destinoLocal, ruta)Archivos de disco. DownloadFileAsync recibe primero el destino local.
DeleteAsync(ruta)Borra en todas, con dos intentos por réplica. true solo si todas confirmaron; si una falla, repite el borrado cuando vuelva.
ResyncAsync(ruta)Fuerza la sincronización de una ruta y devuelve un WriteResult con dónde quedó el objeto.
RepairAsync(ruta)Igual que ResyncAsync, con respuesta bool. Ignora AutoRepair.
CheckDriftAsync(prefijo, sampleCount = 50)Muestrea hasta sampleCount rutas y compara versiones entre réplicas. Solo diagnostica.
HealthCheckAsync()true solo si todas las réplicas responden.

Clients expone la lista de réplicas, AutoRepair activa o desactiva la reparación en lecturas y TotalRepairs cuenta las subidas de reparación.

Deriva entre réplicas

var drift = await multi.CheckDriftAsync("informes/", sampleCount: 50);
if (drift.HasDrift)
    Console.WriteLine($"{drift.Drifted.Count} objetos difieren entre buckets");

CheckDriftAsync devuelve un DriftReport con TotalChecked, InSync, Drifted (rutas que difieren) y Missing (por host, qué rutas faltan). Para corregir, llama a ResyncAsync por cada ruta de Drifted.

La replicación no es transaccional: WriteBytesAsync escribe en todas las réplicas y te devuelve el resultado por host. Comprueba AllOk y repite la escritura en los hosts de FailedHosts cuando vuelvan.

Los archivos que suben los usuarios de Dinaup no pasan por este módulo: van al almacenamiento de la plataforma con File_UploadBytesAsync del Cliente Dinaup.

En esta página